Repository: mdn/kuma

Issue:
Redirect Firefox-specific WebExtensions docs to Extension Workshop 
The Firefox Add-ons team has created [Extension Workshop](https://extensionworkshop.com), a site for extension developers to quickly find information related to developing Firefox Extensions. To make sure developers find the most current information, we need to redirect those pages on MDN to point to Extension Workshop. 

The list of affected pages can be found on this [spreadsheet](https://docs.google.com/spreadsheets/d/15XxPb3mgnvOjWYqcySQMiBAdfsgfEJ5RHx_B5YPTaKI/edit#gid=1796998978). 

MDN pages that are redirected should also be updated to replace existing content with a brief message that the content can be found on Extension Workshop. 

**Due Date**
It would be great if the stage-to-stage test could be run by Wednesday, September 18 to give EW QA time to flag any issues. **edited to change date to September 18 at 9:13am PST**

October 1 is EW's intended "go live" date. Once the prod site is turned on, we would ask that the redirects be implemented on MDN prod. 

**Additional context**
For additional context, here's EW's [pre-flight checklist](https://docs.google.com/document/d/1BrWkiWFe5nysxEd14Vdb9jMhw8VYUKrMbYrPnRN27L0/edit#). 

/ cc @muffinresearch



Discussion:

Resolving patch:
diff --git a/kuma/redirects/redirects.py b/kuma/redirects/redirects.py
--- a/kuma/redirects/redirects.py
+++ b/kuma/redirects/redirects.py
@@ -832,40 +832,129 @@ def locale_redirect(pattern, to, prepend_locale=True, **kwargs):
     'https://marionette-client.readthedocs.io/en/latest/')
 marionette_docs_root_url = (
     'https://firefox-source-docs.mozilla.org/testing/marionette/marionette/')
-marionette_redirect = partial(locale_redirect, re_flags='i',
-                              prepend_locale=False, permanent=True)
+external_redirect = partial(locale_redirect, re_flags='i',
+                            prepend_locale=False, permanent=True)
 
 marionette_redirectpatterns = [
-    marionette_redirect(r'docs/(?:Mozilla/QA/)?Marionette$',
-                        marionette_docs_root_url + 'index.html'),
-    marionette_redirect(r'docs/(?:Mozilla/QA/)?Marionette/Builds$',
-                        marionette_docs_root_url + 'Building.html'),
-    marionette_redirect(r'docs/(?:Mozilla/QA/)?Marionette/Client$',
-                        marionette_client_docs_url),
-    marionette_redirect(r'docs/Mozilla/QA/Marionette/Python_Client$',
-                        marionette_client_docs_url),
-    marionette_redirect(r'docs/(?:Mozilla/QA/)?Marionette/Developer_setup$',
-                        marionette_docs_root_url + 'Contributing.html'),
-    marionette_redirect(r'docs/Marionette_Test_Runner$',
-                        marionette_docs_root_url + 'PythonTests.html'),
-    marionette_redirect(r'docs/Mozilla/QA/Marionette/Marionette_Test_Runner$',
-                        marionette_docs_root_url + 'PythonTests.html'),
-    marionette_redirect(r'docs/(?:Mozilla/QA/)?Marionette/(?:MarionetteTestCase'
-                        r'|Marionette_Python_Tests|Running_Tests|Tests)$',
-                        marionette_docs_root_url + 'PythonTests.html'),
-    marionette_redirect(r'docs/Mozilla/QA/Marionette/Protocol$',
-                        marionette_docs_root_url + 'Protocol.html'),
-    marionette_redirect(r'docs/Mozilla/QA/Marionette/WebDriver/status$',
-                        'https://bugzilla.mozilla.org'
-                        '/showdependencytree.cgi?id=721859&hide_resolved=1'),
-    marionette_redirect(r'docs/Marionette/Debugging$',
-                        marionette_docs_root_url + 'Debugging.html'),
+    external_redirect(r'docs/(?:Mozilla/QA/)?Marionette$',
+                      marionette_docs_root_url + 'index.html'),
+    external_redirect(r'docs/(?:Mozilla/QA/)?Marionette/Builds$',
+                      marionette_docs_root_url + 'Building.html'),
+    external_redirect(r'docs/(?:Mozilla/QA/)?Marionette/Client$',
+                      marionette_client_docs_url),
+    external_redirect(r'docs/Mozilla/QA/Marionette/Python_Client$',
+                      marionette_client_docs_url),
+    external_redirect(r'docs/(?:Mozilla/QA/)?Marionette/Developer_setup$',
+                      marionette_docs_root_url + 'Contributing.html'),
+    external_redirect(r'docs/Marionette_Test_Runner$',
+                      marionette_docs_root_url + 'PythonTests.html'),
+    external_redirect(r'docs/Mozilla/QA/Marionette/Marionette_Test_Runner$',
+                      marionette_docs_root_url + 'PythonTests.html'),
+    external_redirect(r'docs/(?:Mozilla/QA/)?Marionette/(?:MarionetteTestCase'
+                      r'|Marionette_Python_Tests|Running_Tests|Tests)$',
+                      marionette_docs_root_url + 'PythonTests.html'),
+    external_redirect(r'docs/Mozilla/QA/Marionette/Protocol$',
+                      marionette_docs_root_url + 'Protocol.html'),
+    external_redirect(r'docs/Mozilla/QA/Marionette/WebDriver/status$',
+                      'https://bugzilla.mozilla.org'
+                      '/showdependencytree.cgi?id=721859&hide_resolved=1'),
+    external_redirect(r'docs/Marionette/Debugging$',
+                      marionette_docs_root_url + 'Debugging.html'),
+]
+
+webextensions_redirectpatterns = [
+    external_redirect(
+        r'docs/Mozilla/Add-ons/{}$'.format(ao_path),
+        'https://extensionworkshop.com/documentation/' + ew_path
+    ) for ao_path, ew_path in (
+        ('WebExtensions/Security_best_practices',
+         'develop/build-a-secure-extension/'),
+        ('WebExtensions/user_interface/Accessibility_guidelines',
+         'develop/build-an-accessible-extension/'),
+        ('WebExtensions/onboarding_upboarding_offboarding_best_practices',
+         'develop/onboard-upboard-offboard-users/'),
+        ('WebExtensions/Porting_a_Google_Chrome_extension',
+         'develop/porting-a-google-chrome-extension/'),
+        ('WebExtensions/Porting_a_legacy_Firefox_add-on',
+         'develop/porting-a-legacy-firefox-extension/'),
+        ('WebExtensions/Comparison_with_the_Add-on_SDK',
+         'develop/comparison-with-the-add-on-sdk/'),
+        ('WebExtensions/Comparison_with_XUL_XPCOM_extensions',
+         'develop/comparison-with-xul-xpcom-extensions/'),
+        ('WebExtensions/Differences_between_desktop_and_Android',
+         'develop/differences-between-desktop-and-android-extensions/'),
+        ('WebExtensions/Development_Tools',
+         'develop/browser-extension-development-tools/'),
+        ('WebExtensions/Choose_a_Firefox_version_for_web_extension_develop',
+         'develop/choosing-a-firefox-version-for-extension-development/'),
+        ('WebExtensions/User_experience_best_practices',
+         'develop/user-experience-best-practices/'),
+        ('WebExtensions/Prompt_users_for_data_and_privacy_consents',
+         'develop/best-practices-for-collecting-user-data-consents/'),
+        ('WebExtensions/Temporary_Installation_in_Firefox',
+         'develop/temporary-installation-in-firefox/'),
+        ('WebExtensions/Debugging',
+         'develop/debugging/'),
+        ('WebExtensions/Testing_persistent_and_restart_features',
+         'develop/testing-persistent-and-restart-features/'),
+        ('WebExtensions/Test_permission_requests',
+         'develop/test-permission-requests/'),
+        ('WebExtensions/Developing_WebExtensions_for_Firefox_for_Android',
+         'develop/developing-extensions-for-firefox-for-android/'),
+        ('WebExtensions/Getting_started_with_web-ext',
+         'develop/getting-started-with-web-ext/'),
+        ('WebExtensions/web-ext_command_reference',
+         'develop/web-ext-command-reference/'),
+        ('WebExtensions/WebExtensions_and_the_Add-on_ID',
+         'develop/extensions-and-the-add-on-id/'),
+        ('WebExtensions/Request_the_right_permissions',
+         'develop/request-the-right-permissions/'),
+        ('WebExtensions/Best_practices_for_updating_your_extension',
+         'manage/best-practices-for-updating/'),
+        ('Updates',
+         'manage/updating-your-extension/'),
+        ('WebExtensions/Distribution_options',
+         'publish/signing-and-distribution-overview/'),
+        ('Themes/Using_the_AMO_theme_generator',
+         'themes/using-the-amo-theme-generator/'),
+        ('WebExtensions/Developer_accounts',
+         'publish/developer-accounts/'),
+        ('Distribution',
+         'publish/signing-and-distribution-overview/#distributing-your-addon'),
+        ('WebExtensions/Package_your_extension_',
+         'publish/package-your-extension/'),
+        ('Distribution/Submitting_an_add-on',
+         'publish/submitting-an-add-on/'),
+        ('Source_Code_Submission',
+         'publish/source-code-submission/'),
+        ('Distribution/Resources_for_publishers',
+         'manage/resources-for-publishers/'),
+        ('Listing',
+         'develop/create-an-appealing-listing/'),
+        ('Distribution/Make_money_from_browser_extensions',
+         'publish/make-money-from-browser-extensions/'),
+        ('Distribution/Promoting_your_extension_or_theme',
+         'publish/promoting-your-extension/'),
+        ('AMO/Policy/Reviews',
+         'publish/add-on-policies/'),
+        ('AMO/Policy/Agreement',
+         'publish/firefox-add-on-distribution-agreement/'),
+        ('Distribution/Retiring_your_extension',
+         'manage/retiring-your-extension/'),
+        ('WebExtensions/Distribution_options/Sideloading_add-ons',
+         'publish/distribute-sideloading/'),
+        ('WebExtensions/Distribution_options/Add-ons_for_desktop_apps',
+         'publish/distribute-for-desktop-apps/'),
+        ('WebExtensions/Distribution_options/Add-ons_in_the_enterprise',
+         'enterprise/'),
+    )
 ]
 
 redirectpatterns = (
     scl3_redirectpatterns +
     zone_redirectpatterns +
     marionette_redirectpatterns +
+    webextensions_redirectpatterns +
     [
         locale_redirect(
             r'^fellowship',


Test patch:
diff --git a/tests/headless/map_301.py b/tests/headless/map_301.py
--- a/tests/headless/map_301.py
+++ b/tests/headless/map_301.py
@@ -473,3 +473,91 @@
     url_test(marionette_locales + '/docs/Marionette/Debugging',
              marionette_docs_root_url + 'Debugging.html'),
 )))
+
+WEBEXT_URLS = list(flatten(
+    url_test(
+        '{/en-US,/fr,}/docs/Mozilla/Add-ons/' + ao_path,
+        'https://extensionworkshop.com/documentation/' + ew_path
+    ) for ao_path, ew_path in (
+        ('WebExtensions/Security_best_practices',
+         'develop/build-a-secure-extension/'),
+        ('WebExtensions/user_interface/Accessibility_guidelines',
+         'develop/build-an-accessible-extension/'),
+        ('WebExtensions/onboarding_upboarding_offboarding_best_practices',
+         'develop/onboard-upboard-offboard-users/'),
+        ('WebExtensions/Porting_a_Google_Chrome_extension',
+         'develop/porting-a-google-chrome-extension/'),
+        ('WebExtensions/Porting_a_legacy_Firefox_add-on',
+         'develop/porting-a-legacy-firefox-extension/'),
+        ('WebExtensions/Comparison_with_the_Add-on_SDK',
+         'develop/comparison-with-the-add-on-sdk/'),
+        ('WebExtensions/Comparison_with_XUL_XPCOM_extensions',
+         'develop/comparison-with-xul-xpcom-extensions/'),
+        ('WebExtensions/Differences_between_desktop_and_Android',
+         'develop/differences-between-desktop-and-android-extensions/'),
+        ('WebExtensions/Development_Tools',
+         'develop/browser-extension-development-tools/'),
+        ('WebExtensions/Choose_a_Firefox_version_for_web_extension_develop',
+         'develop/choosing-a-firefox-version-for-extension-development/'),
+        ('WebExtensions/User_experience_best_practices',
+         'develop/user-experience-best-practices/'),
+        ('WebExtensions/Prompt_users_for_data_and_privacy_consents',
+         'develop/best-practices-for-collecting-user-data-consents/'),
+        ('WebExtensions/Temporary_Installation_in_Firefox',
+         'develop/temporary-installation-in-firefox/'),
+        ('WebExtensions/Debugging',
+         'develop/debugging/'),
+        ('WebExtensions/Testing_persistent_and_restart_features',
+         'develop/testing-persistent-and-restart-features/'),
+        ('WebExtensions/Test_permission_requests',
+         'develop/test-permission-requests/'),
+        ('WebExtensions/Developing_WebExtensions_for_Firefox_for_Android',
+         'develop/developing-extensions-for-firefox-for-android/'),
+        ('WebExtensions/Getting_started_with_web-ext',
+         'develop/getting-started-with-web-ext/'),
+        ('WebExtensions/web-ext_command_reference',
+         'develop/web-ext-command-reference/'),
+        ('WebExtensions/WebExtensions_and_the_Add-on_ID',
+         'develop/extensions-and-the-add-on-id/'),
+        ('WebExtensions/Request_the_right_permissions',
+         'develop/request-the-right-permissions/'),
+        ('WebExtensions/Best_practices_for_updating_your_extension',
+         'manage/best-practices-for-updating/'),
+        ('Updates',
+         'manage/updating-your-extension/'),
+        ('WebExtensions/Distribution_options',
+         'publish/signing-and-distribution-overview/'),
+        ('Themes/Using_the_AMO_theme_generator',
+         'themes/using-the-amo-theme-generator/'),
+        ('WebExtensions/Developer_accounts',
+         'publish/developer-accounts/'),
+        ('Distribution',
+         'publish/signing-and-distribution-overview/#distributing-your-addon'),
+        ('WebExtensions/Package_your_extension_',
+         'publish/package-your-extension/'),
+        ('Distribution/Submitting_an_add-on',
+         'publish/submitting-an-add-on/'),
+        ('Source_Code_Submission',
+         'publish/source-code-submission/'),
+        ('Distribution/Resources_for_publishers',
+         'manage/resources-for-publishers/'),
+        ('Listing',
+         'develop/create-an-appealing-listing/'),
+        ('Distribution/Make_money_from_browser_extensions',
+         'publish/make-money-from-browser-extensions/'),
+        ('Distribution/Promoting_your_extension_or_theme',
+         'publish/promoting-your-extension/'),
+        ('AMO/Policy/Reviews',
+         'publish/add-on-policies/'),
+        ('AMO/Policy/Agreement',
+         'publish/firefox-add-on-distribution-agreement/'),
+        ('Distribution/Retiring_your_extension',
+         'manage/retiring-your-extension/'),
+        ('WebExtensions/Distribution_options/Sideloading_add-ons',
+         'publish/distribute-sideloading/'),
+        ('WebExtensions/Distribution_options/Add-ons_for_desktop_apps',
+         'publish/distribute-for-desktop-apps/'),
+        ('WebExtensions/Distribution_options/Add-ons_in_the_enterprise',
+         'enterprise/'),
+    )
+))
diff --git a/tests/headless/test_redirects.py b/tests/headless/test_redirects.py
--- a/tests/headless/test_redirects.py
+++ b/tests/headless/test_redirects.py
@@ -4,8 +4,9 @@
 
 from utils.urls import assert_valid_url
 
-from .map_301 import (GITHUB_IO_URLS, LEGACY_URLS, MARIONETTE_URLS, MOZILLADEMOS_URLS, REDIRECT_URLS,
-                      SCL3_REDIRECT_URLS, ZONE_REDIRECT_URLS)
+from .map_301 import (GITHUB_IO_URLS, LEGACY_URLS, MARIONETTE_URLS,
+                      MOZILLADEMOS_URLS, REDIRECT_URLS, SCL3_REDIRECT_URLS,
+                      WEBEXT_URLS, ZONE_REDIRECT_URLS)
 
 # while these test methods are similar, they're each testing a
 # subset of redirects, and it was easier to work with them separately.
@@ -72,3 +73,12 @@ def test_zone_redirects(url, base_url):
 def test_marionette_redirects(url, base_url):
     url['base_url'] = base_url
     assert_valid_url(**url)
+
+
+@pytest.mark.headless
+@pytest.mark.nondestructive
+@pytest.mark.parametrize('url', WEBEXT_URLS,
+                         ids=[item['url'] for item in WEBEXT_URLS])
+def test_webext_redirects(url, base_url):
+    url['base_url'] = base_url
+    assert_valid_url(**url)