diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 494bf9e07a..2e96434975 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -35,14 +35,14 @@ jobs:
- uses: actions/checkout@v3
- uses: ./.github/actions/prepare-playground
- run: ./node_modules/.bin/cypress install --force
- - run: npx nx affected --target=e2e --configuration=ci
+ - run: npx nx affected --target=e2e --configuration=ci --verbose
build:
runs-on: ubuntu-latest
needs: [lint-and-typecheck]
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/prepare-playground
- - run: npx nx affected --target=build --parallel=3
+ - run: npx nx affected --target=build --parallel=3 --verbose
# Deploy documentation job
deploy_docs:
diff --git a/packages/docs/site/docs/08-query-api/01-index.md b/packages/docs/site/docs/08-query-api/01-index.md
index 1a2ddce654..916062f9cc 100644
--- a/packages/docs/site/docs/08-query-api/01-index.md
+++ b/packages/docs/site/docs/08-query-api/01-index.md
@@ -27,7 +27,7 @@ You can go ahead and try it out. The Playground will automatically install the t
| `wp` | `latest` | Loads the specified WordPress version. Supported values: `5.9`, `6.0`, `6.1`, `6.2`, `6.3`, `latest`, `nightly`, `beta` |
| `blueprint-url` | | The URL of the Blueprint that will be used to configure this Playground instance. |
| `php-extension-bundle` | | Loads a bundle of PHP extensions. Supported bundles: `kitchen-sink` (for gd, mbstring, iconv, libxml, xml, dom, simplexml, xmlreader, xmlwriter) |
-| `networking` | `yes` or `no` | Enables or disables the networking support for Playground. Defaults to `yes` |
+| `networking` | `yes` or `no` | Enables or disables the networking support for Playground. Defaults to `no` |
| `plugin` | | Installs the specified plugin. Use the plugin name from the plugins directory URL, e.g. for a URL like `https://wordpress.org/plugins/wp-lazy-loading/`, the plugin name would be `wp-lazy-loading`. You can pre-install multiple plugins by saying `plugin=coblocks&plugin=wp-lazy-loading&…`. Installing a plugin automatically logs the user in as an admin |
| `theme` | | Installs the specified theme. Use the theme name from the themes directory URL, e.g. for a URL like `https://wordpress.org/themes/disco/`, the theme name would be `disco`. Installing a theme automatically logs the user in as an admin |
| `url` | `/wp-admin/` | Load the specified initial page displaying WordPress |
diff --git a/packages/docs/site/docs/09-blueprints-api/03-data-format.md b/packages/docs/site/docs/09-blueprints-api/03-data-format.md
index eb8e22509a..109586a8fc 100644
--- a/packages/docs/site/docs/09-blueprints-api/03-data-format.md
+++ b/packages/docs/site/docs/09-blueprints-api/03-data-format.md
@@ -63,4 +63,4 @@ The `phpExtensionBundles` property is an array of PHP extension bundles to load.
The `features` property is used to enable or disable certain features of the Playground. It can contain the following properties:
-- `networking`: Defaults to `true`. Enables or disables the networking support for Playground. If enabled, `wp_safe_remote_get` and similar WordPress functions will actually use `fetch()` to make HTTP requests. If disabled, they will immediately fail instead.
+- `networking`: Defaults to `false`. Enables or disables the networking support for Playground. If enabled, `wp_safe_remote_get` and similar WordPress functions will actually use `fetch()` to make HTTP requests. If disabled, they will immediately fail instead.
diff --git a/packages/docs/site/docs/11-architecture/02-wasm-php-overview.md b/packages/docs/site/docs/11-architecture/02-wasm-php-overview.md
index b169dd422b..b860d80c7f 100644
--- a/packages/docs/site/docs/11-architecture/02-wasm-php-overview.md
+++ b/packages/docs/site/docs/11-architecture/02-wasm-php-overview.md
@@ -20,4 +20,4 @@ When it comes to networking, WebAssembly programs are limited to calling JavaScr
In Node.js, the answer involves a WebSocket to TCP socket proxy, [Asyncify](https://emscripten.org/docs/porting/asyncify.html), and patching deep PHP internals like php_select. It's complex, but there's a reward. The Node.js-targeted PHP build can request web APIs, install composer packages, and even connect to a MySQL server.
-In the browser, networking is not supported yet. Initiating a HTTPS connection involves opening a raw TCP socket which is not possible in the browser. There is an [open GitHub issue](https://github.com/WordPress/wordpress-playground/issues/85) that explores possible ways of addressing this problem.
+In the browser, networking is supported to a limited extent. Network calls initiated using `wp_safe_remote_get`, like the ones in the plugin directory or the font library, are translated into `fetch()` calls and succeed if the remote server sends the correct CORS headers. However, a full support for arbitrary HTTPS connection involves opening a raw TCP socket which is not possible in the browser. There is an [open GitHub issue](https://github.com/WordPress/wordpress-playground/issues/85) that explores possible ways of addressing this problem.
diff --git a/packages/playground/blueprints/src/lib/compile.ts b/packages/playground/blueprints/src/lib/compile.ts
index 21e0dc5e56..1feef66572 100644
--- a/packages/playground/blueprints/src/lib/compile.ts
+++ b/packages/playground/blueprints/src/lib/compile.ts
@@ -167,8 +167,8 @@ export function compileBlueprint(
blueprint.phpExtensionBundles || []
),
features: {
- // Enable networking by default
- networking: blueprint.features?.networking ?? true,
+ // Disable networking by default
+ networking: blueprint.features?.networking ?? false,
},
run: async (playground: UniversalPHP) => {
try {
diff --git a/packages/playground/blueprints/src/lib/steps/apply-wordpress-patches/wp-content/mu-plugins/2-nice-error-messages-for-plugins-and-themes-directories.php b/packages/playground/blueprints/src/lib/steps/apply-wordpress-patches/wp-content/mu-plugins/2-nice-error-messages-for-plugins-and-themes-directories.php
index 7ab5405410..d367b1a320 100644
--- a/packages/playground/blueprints/src/lib/steps/apply-wordpress-patches/wp-content/mu-plugins/2-nice-error-messages-for-plugins-and-themes-directories.php
+++ b/packages/playground/blueprints/src/lib/steps/apply-wordpress-patches/wp-content/mu-plugins/2-nice-error-messages-for-plugins-and-themes-directories.php
@@ -12,7 +12,7 @@
if($res instanceof WP_Error) {
$res = new WP_Error(
'plugins_api_failed',
- 'Playground does not yet support connecting to the plugin directory yet. You can still upload plugins or install them using the Query API (e.g. ?plugin=coblocks).'
+ 'Enable networking support in Playground settings to access the Plugins directory. Network access is an experimental, opt-in feature. If you don\'t want to use it, you can still upload plugins or install them using the Query API (e.g. ?plugin=coblocks).'
);
}
return $res;
@@ -29,7 +29,7 @@
}
if($translation === 'An unexpected error occurred. Something may be wrong with WordPress.org or this server’s configuration. If you continue to have problems, please try the support forums.') {
- return 'Playground does not yet support connecting to the themes directory yet. You can still upload a theme or install it using the Query API (e.g. ?theme=pendant).';
+ return 'Enable networking support in Playground settings to access the Themes directory. Network access is an experimental, opt-in feature. If you don\'t want to use it, you can still upload themes or install them using the Query API (e.g. ?theme=pendant).';
}
return $translation;
} );
diff --git a/packages/playground/website/cypress/e2e/app.cy.ts b/packages/playground/website/cypress/e2e/app.cy.ts
index f2a13e4b35..945c52058e 100644
--- a/packages/playground/website/cypress/e2e/app.cy.ts
+++ b/packages/playground/website/cypress/e2e/app.cy.ts
@@ -33,7 +33,7 @@ describe('Query API', () => {
describe('option `wp`', () => {
it('should load WordPress latest by default', () => {
- cy.visit('/?networking=no&url=/wp-admin/');
+ cy.visit('/?url=/wp-admin/');
const expectedBodyClass =
'branch-' + LatestSupportedWordPressVersion.replace('.', '-');
cy.wordPressDocument()
@@ -42,16 +42,14 @@ describe('Query API', () => {
});
it('should load WordPress 6.3 when requested', () => {
- cy.visit('/?networking=no&wp=6.3&url=/wp-admin');
+ cy.visit('/?wp=6.3&url=/wp-admin');
cy.wordPressDocument().find(`body.branch-6-3`).should('exist');
});
});
describe('option `php-extension-bundle`', () => {
it('should load the specified PHP extensions', () => {
- cy.visit(
- '/?networking=no&php-extension-bundle=kitchen-sink&url=/phpinfo.php'
- );
+ cy.visit('/?php-extension-bundle=kitchen-sink&url=/phpinfo.php');
cy.wordPressDocument()
.its('body')
.should('contain', '--enable-xmlwriter');
@@ -60,13 +58,10 @@ describe('Query API', () => {
describe('option `networking`', () => {
it('should disable networking when requested', () => {
- cy.visit('/?networking=no&url=/wp-admin/plugin-install.php');
+ cy.visit('/?url=/wp-admin/plugin-install.php');
cy.wordPressDocument()
.find('.notice.error')
- .should(
- 'contain',
- 'does not yet support connecting to the plugin directory'
- );
+ .should('contain', 'Enable networking support in Playground');
});
it('should enable networking when requested', () => {
@@ -97,7 +92,7 @@ describe('Query API', () => {
describe('option `url`', () => {
it('should load the specified URL', () => {
- cy.visit('/?url=/wp-admin/&networking=no');
+ cy.visit('/?url=/wp-admin/');
cy.wordpressPath().should('contain', '/wp-admin/');
cy.wordPressDocument()
.find('#adminmenu')
@@ -107,11 +102,11 @@ describe('Query API', () => {
describe('option `mode`', () => {
it('lack of mode=seamless should a WordPress in a simulated browser UI', () => {
- cy.visit('/?networking=no');
+ cy.visit('/');
cy.get('[data-cy="simulated-browser"]').should('exist');
});
it('mode=seamless should load a fullscreen WordPress', () => {
- cy.visit('/?networking=no&mode=seamless');
+ cy.visit('/?mode=seamless');
cy.get('[data-cy="simulated-browser"]').should('not.exist');
});
});
@@ -187,7 +182,7 @@ describe('Query API', () => {
});
describe('playground-website', () => {
- beforeEach(() => cy.visit('/?networking=no'));
+ beforeEach(() => cy.visit('/'));
it('should reflect the URL update from the navigation bar in the WordPress site', () => {
cy.setWordPressUrl('/wp-admin');
@@ -195,83 +190,141 @@ describe('playground-website', () => {
});
// Test all PHP versions for completeness
- SupportedPHPVersions.forEach((version) => {
- it('should switch PHP version to ' + version, () => {
+ describe('PHP version switcher', () => {
+ SupportedPHPVersions.forEach((version) => {
+ it('should switch PHP version to ' + version, () => {
+ // Update settings in Playground configurator
+ cy.get('button#configurator').click();
+ cy.get('select#php-version').select(version);
+ cy.get('#modal-content button[type=submit]').click();
+ // Wait for the page to finish reloading
+ cy.url().should('contain', `php=${version}`);
+ cy.document().should('exist');
+
+ // Go to phpinfo
+ cy.setWordPressUrl('/phpinfo.php');
+ cy.wordPressDocument()
+ .find('h1')
+ .should('contain', 'PHP Version ' + version);
+ });
+ });
+ });
+
+ // Only test the latest PHP version to save time
+ describe('PHP extensions bundle', () => {
+ it('should load additional PHP extensions when requested', () => {
// Update settings in Playground configurator
cy.get('button#configurator').click();
- cy.get('select#php-version').select(version);
+ cy.get('select#php-version').select(LatestSupportedPHPVersion);
+ cy.get('input[name=with-extensions]').check();
cy.get('#modal-content button[type=submit]').click();
- // Wait for the page to finish reloading
- cy.url().should('contain', `&php=${version}`);
+ // Wait for the page to finish loading
cy.document().should('exist');
// Go to phpinfo
cy.setWordPressUrl('/phpinfo.php');
cy.wordPressDocument()
- .find('h1')
- .should('contain', 'PHP Version ' + version);
+ .its('body')
+ .should('contain', '--enable-xmlwriter');
+ });
+
+ it('should not load additional PHP extensions when not requested', () => {
+ // Update settings in Playground configurator
+ cy.get('button#configurator').click();
+ cy.get('select#php-version').select(LatestSupportedPHPVersion);
+ cy.get('#modal-content button[type=submit]').click();
+ // Wait for the page to finish loading
+ cy.document().should('exist');
+
+ // Go to phpinfo
+ cy.setWordPressUrl('/phpinfo.php');
+ cy.wordPressDocument()
+ .its('body')
+ .should('contain', '--without-libxml');
});
});
- // Only test the latest PHP version to save time
- it('should load additional PHP extensions when requested', () => {
- // Update settings in Playground configurator
+ // Test all WordPress versions for completeness
+ describe('WordPress version selector', () => {
+ for (const version in SupportedWordPressVersions) {
+ if (version === 'beta') {
+ continue;
+ }
+ // @ts-ignore
+ let versionMessage = 'Version ' + version;
+ if (version === 'nightly') {
+ versionMessage = 'You are using a development version';
+ }
+
+ it('should switch WordPress version to ' + version, () => {
+ // Update settings in Playground configurator
+ cy.get('button#configurator').click();
+ cy.get(`select#wp-version option[value="${version}"]`).should(
+ 'exist'
+ );
+ cy.get('select#wp-version').select(`${version}`);
+ cy.get('#modal-content button[type=submit]').click();
+ // Wait for the page to finish loading
+ cy.url().should('contain', `&wp=${version}`);
+
+ // Go to phpinfo
+ cy.setWordPressUrl('/wp-admin');
+ cy.wordPressDocument()
+ .find('#footer-upgrade')
+ .should('contain', versionMessage);
+ });
+ }
+ });
+});
+
+/**
+ * These tests only check if the modal UI updates the URL correctly.
+ * The actual networking functionality is tested in the Query API tests.
+ */
+describe('Website UI – Networking support', () => {
+ it('should display an unchecked networking checkbox by default', () => {
+ cy.visit('/');
+
cy.get('button#configurator').click();
- cy.get('select#php-version').select(LatestSupportedPHPVersion);
- cy.get('input[name=with-extensions]').check();
- cy.get('#modal-content button[type=submit]').click();
- // Wait for the page to finish loading
- cy.document().should('exist');
+ cy.get('input[name=with-networking]').should('not.be.checked');
+ });
- // Go to phpinfo
- cy.setWordPressUrl('/phpinfo.php');
- cy.wordPressDocument()
- .its('body')
- .should('contain', '--enable-xmlwriter');
+ it('should display a checked networking checkbox when networking is enabled', () => {
+ cy.visit('/?networking=yes');
+
+ cy.get('button#configurator').click();
+ cy.get('input[name=with-networking]').should('be.checked');
});
- it('should not load additional PHP extensions when not requested', () => {
+ it('should enable networking when requested', () => {
+ cy.visit('/');
+
// Update settings in Playground configurator
cy.get('button#configurator').click();
- cy.get('select#php-version').select(LatestSupportedPHPVersion);
+ cy.get('input[name=with-networking]').check();
cy.get('#modal-content button[type=submit]').click();
- // Wait for the page to finish loading
+
+ // Wait for the page to reload
cy.document().should('exist');
+ cy.get('#modal-content button[type=submit]').should('not.exist');
- // Go to phpinfo
- cy.setWordPressUrl('/phpinfo.php');
- cy.wordPressDocument()
- .its('body')
- .should('contain', '--without-libxml');
+ // Confirm the URL was updated correctly
+ cy.relativeUrl().should('contain', 'networking=yes');
});
- // Test all WordPress versions for completeness
- for (const version in SupportedWordPressVersions) {
- if (version === 'beta') {
- continue;
- }
- // @ts-ignore
- let versionMessage = 'Version ' + version;
- if (version === 'nightly') {
- versionMessage = 'You are using a development version';
- }
+ it('should disable networking when requested', () => {
+ cy.visit('/?networking=yes');
- it('should switch WordPress version to ' + version, () => {
- // Update settings in Playground configurator
- cy.get('button#configurator').click();
- cy.get(`select#wp-version option[value="${version}"]`).should(
- 'exist'
- );
- cy.get('select#wp-version').select(`${version}`);
- cy.get('#modal-content button[type=submit]').click();
- // Wait for the page to finish loading
- cy.url().should('contain', `&wp=${version}`);
+ // Update settings in Playground configurator
+ cy.get('button#configurator').click();
+ cy.get('input[name=with-networking]').uncheck();
+ cy.get('#modal-content button[type=submit]').click();
- // Go to phpinfo
- cy.setWordPressUrl('/wp-admin');
- cy.wordPressDocument()
- .find('#footer-upgrade')
- .should('contain', versionMessage);
- });
- }
+ // Wait for the page to reload
+ cy.document().should('exist');
+ cy.get('#modal-content button[type=submit]').should('not.exist');
+
+ // Confirm the URL was updated correctly
+ cy.relativeUrl().should('not.contain', 'networking=yes');
+ });
});
diff --git a/packages/playground/website/cypress/support/commands.ts b/packages/playground/website/cypress/support/commands.ts
index 16407764fa..595fcbf052 100644
--- a/packages/playground/website/cypress/support/commands.ts
+++ b/packages/playground/website/cypress/support/commands.ts
@@ -17,6 +17,7 @@ declare namespace Cypress {
setWordPressUrl(url: string): void;
wordPressDocument(): Chainable;
wordpressPath(): Chainable;
+ relativeUrl(): Chainable;
}
}
@@ -45,3 +46,12 @@ Cypress.Commands.add('wordpressPath', () => {
.find('#wp')
.its('0.contentWindow.location.pathname');
});
+
+Cypress.Commands.add('relativeUrl', () => {
+ // relative part of the current top-level URL
+ return cy.url().then((href) => {
+ const url = new URL(href);
+ console.log({ href });
+ return href.substring(url.origin.length);
+ });
+});
diff --git a/packages/playground/website/project.json b/packages/playground/website/project.json
index 93ce3ee596..783036eb63 100644
--- a/packages/playground/website/project.json
+++ b/packages/playground/website/project.json
@@ -37,7 +37,8 @@
"mode": "development"
},
"production": {
- "mode": "production"
+ "mode": "production",
+ "logLevel": "info"
}
},
"dependsOn": ["^build"]
diff --git a/packages/playground/website/src/components/playground-configuration-group/form.tsx b/packages/playground/website/src/components/playground-configuration-group/form.tsx
index 291a93573e..ca4c6fc901 100644
--- a/packages/playground/website/src/components/playground-configuration-group/form.tsx
+++ b/packages/playground/website/src/components/playground-configuration-group/form.tsx
@@ -13,6 +13,7 @@ export interface PlaygroundConfiguration {
wp: string;
php: SupportedPHPVersion;
withExtensions: boolean;
+ withNetworking: boolean;
storage: StorageType;
resetSite?: boolean;
}
@@ -44,6 +45,9 @@ export function PlaygroundConfigurationForm({
const [withExtensions, setWithExtensions] = useState(
initialData.withExtensions
);
+ const [withNetworking, setWithNetworking] = useState(
+ initialData.withNetworking
+ );
const [wp, setWp] = useState(initialData.wp);
const handleStorageChange = async (
event: React.ChangeEvent
@@ -69,7 +73,8 @@ export function PlaygroundConfigurationForm({
storage,
wp,
resetSite,
- withExtensions: withExtensions,
+ withExtensions,
+ withNetworking,
});
}
@@ -280,6 +285,20 @@ export function PlaygroundConfigurationForm({
/>
Load extensions: libxml, mbstring, iconv, gd
+
{/*
- * Without an empty option, React sometimes says
- * the current selected version is "nightly" when
- * `wp` is actually "6.4".
- */}
+ * Without an empty option, React sometimes says
+ * the current selected version is "nightly" when
+ * `wp` is actually "6.4".
+ */}
{Object.keys(supportedWPVersions).map((version) => (