diff --git a/.distignore b/.distignore index c7dd8d7..dbd8f08 100644 --- a/.distignore +++ b/.distignore @@ -1,7 +1,9 @@ /.git /.github /.wordpress-org +/artifacts /node_modules +/test /vendor .distignore .editorconfig @@ -16,6 +18,7 @@ .wp-env.json composer.json composer.lock +playwright.config.ts package-lock.json package.json phpcs.ruleset.xml diff --git a/.github/workflows/run-test-and-deploy.yml b/.github/workflows/run-test-and-deploy.yml index ee246a5..a43b81b 100644 --- a/.github/workflows/run-test-and-deploy.yml +++ b/.github/workflows/run-test-and-deploy.yml @@ -10,6 +10,23 @@ on: jobs: test: runs-on: ubuntu-latest + strategy: + matrix: + include: + - php: '7.4' + wp: WordPress + - php: '7.4' + wp: WordPress#6.3.2 + - php: '8.0' + wp: WordPress + - php: '8.0' + wp: WordPress#6.3.2 + - php: '8.2' + wp: WordPress + - php: '8.2' + wp: WordPress#6.3.2 + name: PHP ${{ matrix.php }} / ${{ matrix.wp }} Test + steps: - uses: actions/checkout@v3 diff --git a/.github/workflows/run-test.yml b/.github/workflows/run-test.yml index 872a70c..65379f2 100644 --- a/.github/workflows/run-test.yml +++ b/.github/workflows/run-test.yml @@ -13,6 +13,23 @@ on: jobs: test: runs-on: ubuntu-latest + strategy: + matrix: + include: + - php: '7.4' + wp: WordPress + - php: '7.4' + wp: WordPress#6.3.2 + - php: '8.0' + wp: WordPress + - php: '8.0' + wp: WordPress#6.3.2 + - php: '8.2' + wp: WordPress + - php: '8.2' + wp: WordPress#6.3.2 + name: PHP ${{ matrix.php }} / ${{ matrix.wp }} Test + steps: - uses: actions/checkout@v3 @@ -33,3 +50,24 @@ jobs: - name: Running lint check run: npm run lint + + - name: Install Playwright dependencies + run: | + npx playwright install chromium firefox webkit --with-deps + + - name: Install WordPress + run: | + WP_ENV_CORE=WordPress/${{ matrix.wp }} WP_ENV_PHP_VERSION=${{ matrix.php }} npm run wp-env start + npm run wp-env run cli wp core version + npm run wp-env run cli wp cli info + + - name: Running e2e tests + run: npm run test:e2e + + - name: Archive debug artifacts + uses: actions/upload-artifact@v3 + if: always() + with: + name: failures-artifacts + path: artifacts + if-no-files-found: ignore diff --git a/.gitignore b/.gitignore index bf3a898..7fac410 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .vscode +artifacts/ build/ node_modules/ vendor/ diff --git a/package.json b/package.json index 39f5833..17600ac 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,10 @@ "lint:php": "composer lint", "lint:js": "wp-scripts lint-js", "lint:style": "wp-scripts lint-style", - "lint:types": "tsc" + "lint:types": "tsc", + "test": "npm run lint:js && npm run test:e2e", + "test:e2e": "wp-scripts test-playwright", + "test:e2e:debug": "wp-scripts test-playwright --debug" }, "devDependencies": { "@types/wordpress__block-editor": "11.5.5", diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000..30f5cf8 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,8 @@ +const config = require( '@wordpress/scripts/config/playwright.config.js' ); +const { fileURLToPath } = require( 'url' ); + +export default { + ...config, + globalSetup: fileURLToPath( new URL( './test/e2e/global-setup.ts', 'file:' + __filename ).href ), + testDir: './test/e2e', +}; diff --git a/test/e2e/assets/1000x750.png b/test/e2e/assets/1000x750.png new file mode 100644 index 0000000..378d3ea Binary files /dev/null and b/test/e2e/assets/1000x750.png differ diff --git a/test/e2e/assets/400x300.png b/test/e2e/assets/400x300.png new file mode 100644 index 0000000..95c7655 Binary files /dev/null and b/test/e2e/assets/400x300.png differ diff --git a/test/e2e/assets/600x450.png b/test/e2e/assets/600x450.png new file mode 100644 index 0000000..514d7b9 Binary files /dev/null and b/test/e2e/assets/600x450.png differ diff --git a/test/e2e/global-setup.ts b/test/e2e/global-setup.ts new file mode 100644 index 0000000..b63feef --- /dev/null +++ b/test/e2e/global-setup.ts @@ -0,0 +1,40 @@ +/** + * External dependencies + */ +import { request } from '@playwright/test'; +import type { FullConfig } from '@playwright/test'; + +/** + * WordPress dependencies + */ +import { RequestUtils } from '@wordpress/e2e-test-utils-playwright'; + +async function globalSetup( config: FullConfig ) { + const { storageState, baseURL } = config.projects[ 0 ].use; + const storageStatePath = typeof storageState === 'string' ? storageState : undefined; + + const requestContext = await request.newContext( { + baseURL, + } ); + + const requestUtils = new RequestUtils( requestContext, { + storageStatePath, + } ); + + // Authenticate and save the storageState to disk. + await requestUtils.setupRest(); + + // Reset the test environment before running the tests. + await Promise.all( [ + requestUtils.activateTheme( 'twentytwentytwo' ), + // Disable this test plugin as it's conflicting with some of the tests. + // We already have reduced motion enabled and Playwright will wait for most of the animations anyway. + requestUtils.deleteAllPosts(), + requestUtils.deleteAllBlocks(), + requestUtils.resetPreferences(), + ] ); + + await requestContext.dispose(); +} + +export default globalSetup; diff --git a/test/e2e/test.spec.js b/test/e2e/test.spec.js new file mode 100644 index 0000000..643b16d --- /dev/null +++ b/test/e2e/test.spec.js @@ -0,0 +1,126 @@ +/** + * External dependencies + */ +const path = require( 'path' ); +const fs = require( 'fs/promises' ); +const os = require( 'os' ); +const { v4: uuid } = require( 'uuid' ); + +/** + * WordPress dependencies + */ +import { test, expect } from '@wordpress/e2e-test-utils-playwright'; + +test.use( { + mediaUtils: async ( { page }, use ) => { + await use( new MediaUtils( { page } ) ); + }, +} ); + +test.describe( 'Block', () => { + test.beforeEach( async ( { admin } ) => { + await admin.createNewPost(); + } ); + + test( 'should create image block', async ( { editor, page, mediaUtils } ) => { + // Insert Image block. + await editor.insertBlock( { name: 'core/image' } ); + + const imageBlock = editor.canvas.locator( 'role=document[name="Block: Image"i]' ); + await expect( imageBlock ).toBeVisible(); + + // Upload image. + await mediaUtils.upload( + imageBlock.locator( 'data-testid=form-file-upload-input' ), + '1000x750.png' + ); + + // Add first image source. + await editor.openDocumentSettingsSidebar(); + const firstSourceFilename = await mediaUtils.uploadSource( '600x450.png' ); + const firstSource = page.locator( 'role=region[name="Editor settings"i] >> img' ); + await expect( firstSource ).toBeVisible(); + await expect( firstSource ).toHaveAttribute( 'src', new RegExp( firstSourceFilename ) ); + + const enableResponsiveImagePanel = page.locator( '.enable-responsive-image' ); + + // Change first image setting. + await page.fill( 'role=spinbutton[name="Media query value"i]', '800' ); + await enableResponsiveImagePanel.locator( 'role=combobox[name="Resolution"i]' ).selectOption( { + label: 'Medium', + } ); + + // Add second image source. + await page.click( 'role=button[name="Add image source"i]' ); + const secondSourceFilename = await mediaUtils.uploadSource( '400x300.png' ); + const secondSource = page.locator( 'role=region[name="Editor settings"i] >> img' ).nth( 1 ); + await expect( secondSource ).toBeVisible(); + await expect( secondSource ).toHaveAttribute( 'src', new RegExp( secondSourceFilename ) ); + + // Chage second image setting. + await page.locator( 'role=spinbutton[name="Media query value"i]' ).nth( 1 ).fill( '500' ); + await enableResponsiveImagePanel + .locator( 'role=radiogroup[name="Media query type"i]' ) + .nth( 1 ) + .click( 'role=radio[name="min-width"i]' ); + await enableResponsiveImagePanel + .locator( 'role=combobox[name="Resolution"i]' ) + .nth( 1 ) + .selectOption( { + label: 'Thumbnail', + } ); + + const blocks = await editor.getBlocks(); + + await expect.poll( editor.getBlocks ).toMatchObject( [ + { + name: 'core/image', + attributes: { + sources: [ + { + slug: 'medium', + mediaType: 'max-width', + mediaValue: 800, + }, + { + slug: 'thumbnail', + mediaType: 'min-width', + mediaValue: 500, + }, + ], + }, + }, + ] ); + + const sources = blocks[ 0 ].attributes.sources; + expect( sources[ 0 ].srcset.includes( firstSourceFilename ) ).toBe( true ); + expect( sources[ 1 ].srcset.includes( secondSourceFilename ) ).toBe( true ); + } ); +} ); + +class MediaUtils { + constructor( { page } ) { + this.page = page; + this.basePath = path.join( __dirname, 'assets' ); + } + + async upload( inputElement, customFile ) { + const tmpDirectory = await fs.mkdtemp( path.join( os.tmpdir(), 'test-image-' ) ); + const filename = uuid(); + const tmpFileName = path.join( tmpDirectory, filename + '.png' ); + const filepath = path.join( this.basePath, customFile ); + await fs.copyFile( filepath, tmpFileName ); + await inputElement.setInputFiles( tmpFileName ); + return filename; + } + + async uploadSource( customFile ) { + await this.page.click( 'role=button[name="Set image source"i]' ); + const filename = await this.upload( + this.page.locator( '.media-modal .moxie-shim input[type=file]' ), + customFile + ); + await this.page.click( 'role=dialog >> role=button[name="Select"i]' ); + return filename; + } +}