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) => (