Blocks E2E: Refactor configs and workflow (#46409)

This commit is contained in:
Bart Kalisz 2024-05-13 14:58:26 +02:00 committed by GitHub
parent 4840af874a
commit 30756f74e3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
50 changed files with 452 additions and 619 deletions

View File

@ -1,4 +1,4 @@
name: Run Blocks Playwright Tests
name: Blocks Playwright Tests
on:
pull_request:
@ -11,13 +11,12 @@ on:
# Allow manually triggering the workflow.
workflow_dispatch:
env:
FORCE_COLOR: 1
jobs:
e2e:
name: ${{ matrix.config.name }} [${{ matrix.shards.name }}]
blocks-playwright-tests:
name: Shard ${{ matrix.shardIndex }} of ${{ matrix.shardTotal }}
timeout-minutes: 60
runs-on: ubuntu-latest
defaults:
@ -26,27 +25,11 @@ jobs:
strategy:
fail-fast: false
matrix:
config:
- name: Default (Block) Theme
file: playwright.config.ts
resultPath: test-results
- name: Classic Theme
file: playwright.classic-theme.config.ts
resultPath: test-results-classic-theme
- name: Side Effects
file: playwright.side-effects.config.ts
resultPath: test-results-side-effects
- name: Block Theme With Templates
file: playwright.block-theme-with-templates.config.ts
resultPath: test-results-block-theme-with-templates
shards:
- name: 1/5
- name: 2/5
- name: 3/5
- name: 4/5
- name: 5/5
shardIndex: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
shardTotal: [10]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Setup WooCommerce Monorepo
uses: ./.github/actions/setup-woocommerce-monorepo
@ -54,19 +37,36 @@ jobs:
install: '@woocommerce/plugin-woocommerce...'
build: '@woocommerce/plugin-woocommerce'
- name: Install Playwright
run: pnpm --filter='@woocommerce/block-library' exec playwright install --with-deps
- name: Install Playwright dependencies
run: pnpm exec playwright install chromium --with-deps
- name: Start wp-env
run: pnpm --filter='@woocommerce/block-library' env:start
- name: Setup testing environment and start the server
run: pnpm env:start
- name: Run Playwright tests
working-directory: plugins/woocommerce-blocks
run: pnpm playwright test --config=tests/e2e/${{ matrix.config.file }} --shard ${{ matrix.shards.name }}
run: pnpm test:e2e --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
- uses: actions/upload-artifact@v3
if: ${{ failure() }}
- name: Archive debug artifacts (screenshots, traces)
uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: playwright-report-${{ matrix.config.name }}
path: plugins/woocommerce-blocks/tests/e2e/artifacts/${{ matrix.config.resultPath }}
if-no-files-found: error # 'warn' or 'ignore' are also available, defaults to `warn`
name: failures-artifacts-shard-${{ matrix.shardIndex }}
path: plugins/woocommerce-blocks/tests/e2e/artifacts/test-results
if-no-files-found: ignore
merge-artifacts:
# Merges all artifacts from all shards into a single zip and
# deletes the parts. In case of a rerun, artifacts from the
# previous run will be retained by merging them with the new ones.
name: Merge Artifacts
if: ${{ !cancelled() }}
needs: [blocks-playwright-tests]
runs-on: ubuntu-latest
steps:
- name: Merge Artifacts
uses: actions/upload-artifact/merge@v4
# Don't fail the job if there aren't any artifacts to merge.
continue-on-error: true
with:
name: failures-artifacts
delete-merged: true

View File

@ -86,13 +86,9 @@
"test:js": "wp-scripts test-unit-js --config tests/js/jest.config.json",
"test:debug": "ndb .",
"test:e2e": "sh ./bin/check-env.sh && pnpm playwright test --config=tests/e2e/playwright.config.ts",
"test:e2e:report": "sh ./bin/check-env.sh && npx playwright test --config=tests/e2e/playwright.config.ts --reporter=html",
"test:e2e:side-effects": "pnpm run test:e2e --config=tests/e2e/playwright.side-effects.config.ts",
"test:e2e:side-effects:report": "pnpm run test:e2e:report --config=tests/e2e/playwright.side-effects.config.ts --reporter=html",
"test:e2e:classic-theme": "pnpm run test:e2e --config=tests/e2e/playwright.classic-theme.config.ts",
"test:e2e:classic-theme:report": "pnpm run test:e2e:report --config=tests/e2e/playwright.classic-theme.config.ts",
"test:e2e:block-theme-with-templates": "pnpm run test:e2e --config=tests/e2e/playwright.block-theme-with-templates.config.ts",
"test:e2e:block-theme-with-templates:report": "pnpm run test:e2e:report --config=tests/e2e/playwright.block-theme-with-templates.config.ts",
"test:e2e:block-theme": "pnpm run test:e2e block_theme",
"test:e2e:classic-theme": "pnpm run test:e2e classic_theme",
"test:e2e:block-theme-with-templates": "pnpm run test:e2e block_theme_with_templates",
"test:e2e:jest": "pnpm run wp-env:config && cross-env JEST_PUPPETEER_CONFIG=tests/e2e-jest/config/jest-puppeteer.config.js NODE_CONFIG_DIR=tests/e2e-jest/config wp-scripts test-e2e --config tests/e2e-jest/config/jest.config.js",
"test:e2e:jest:dev": "pnpm run wp-env:config && cross-env JEST_PUPPETEER_CONFIG=tests/e2e-jest/config/jest-puppeteer.config-dev.js NODE_CONFIG_DIR=tests/e2e-jest/config wp-scripts test-e2e --config tests/e2e-jest/config/jest.config.js",
"test:e2e:jest:dev-watch": "pnpm run wp-env:config && cross-env JEST_PUPPETEER_CONFIG=tests/e2e-jest/config/jest-puppeteer.config-dev.js NODE_CONFIG_DIR=tests/e2e-jest/config wp-scripts test-e2e --config tests/e2e-jest/config/jest.config.js --watch",

View File

@ -1,19 +0,0 @@
/**
* External dependencies
*/
import {
BLOCK_THEME_WITH_TEMPLATES_SLUG,
DB_EXPORT_FILE,
cli,
} from '@woocommerce/e2e-utils';
import { test as setup, expect } from '@woocommerce/e2e-playwright-utils';
setup( 'Sets up the block theme with templates', async ( { requestUtils } ) => {
await requestUtils.activateTheme( BLOCK_THEME_WITH_TEMPLATES_SLUG );
const cliOutput = await cli(
`npm run wp-env run tests-cli wp db export ${ DB_EXPORT_FILE }`
);
// eslint-disable-next-line playwright/no-standalone-expect
expect( cliOutput.stdout ).toContain( 'Success: Exported' );
} );

View File

@ -1,30 +0,0 @@
/* eslint-disable playwright/no-standalone-expect */
/**
* External dependencies
*/
import { BLOCK_THEME_SLUG, DB_EXPORT_FILE, cli } from '@woocommerce/e2e-utils';
import { test as setup, expect } from '@woocommerce/e2e-playwright-utils';
setup( 'Sets up the block theme', async () => {
let cliOutput = await cli(
`npm run wp-env run tests-cli -- wp theme install ${ BLOCK_THEME_SLUG } --activate`
);
expect(
cliOutput.stdout,
`Could not install and/or activate ${ BLOCK_THEME_SLUG }`
).toContain( 'Success' );
// Enable permalinks and perform a hard flush.
cliOutput = await cli(
`npm run wp-env run tests-cli -- wp rewrite structure /%postname%/ --hard`
);
expect( cliOutput.stdout ).toContain( 'Success: Rewrite structure set' );
expect( cliOutput.stdout ).toContain( 'Success: Rewrite rules flushed' );
cliOutput = await cli(
`npm run wp-env run tests-cli wp db export ${ DB_EXPORT_FILE }`
);
expect( cliOutput.stdout ).toContain( 'Success: Exported' );
} );

View File

@ -1,26 +0,0 @@
/* eslint-disable playwright/no-standalone-expect */
/**
* External dependencies
*/
import {
CLASSIC_THEME_NAME,
CLASSIC_THEME_SLUG,
DB_EXPORT_FILE,
cli,
} from '@woocommerce/e2e-utils';
import { test as setup, expect } from '@woocommerce/e2e-playwright-utils';
setup( 'Sets up the classic theme', async ( { admin } ) => {
await cli(
`npm run wp-env run tests-cli -- wp theme install ${ CLASSIC_THEME_SLUG } --activate`
);
await admin.page.goto( '/wp-admin/themes.php' );
await expect(
admin.page.getByText( `Active: ${ CLASSIC_THEME_NAME }` )
).toBeVisible();
const cliOutput = await cli(
`npm run wp-env run tests-cli wp db export ${ DB_EXPORT_FILE }`
);
expect( cliOutput.stdout ).toContain( 'Success: Exported' );
} );

View File

@ -5,7 +5,14 @@
*/
import { chromium, request } from '@playwright/test';
import { RequestUtils } from '@wordpress/e2e-test-utils-playwright';
import { BASE_URL, adminFile, cli, customerFile } from '@woocommerce/e2e-utils';
import {
BASE_URL,
adminFile,
cli,
customerFile,
BLOCK_THEME_SLUG,
DB_EXPORT_FILE,
} from '@woocommerce/e2e-utils';
/**
* Internal dependencies
@ -53,36 +60,51 @@ const prepareAttributes = async () => {
};
async function globalSetup() {
const timers = {
total: '└ Total time',
authentication: '├ Authentication time',
attributes: '├ Attributes preparation time',
};
console.log( 'Running global setup:' );
console.time( '└ Total time' );
console.log( 'Running global setup...' );
console.time( timers.total );
let databaseImported = false;
try {
console.log( '├ Attempting database import…' );
await cli(
`npm run wp-env run tests-cli wp db import ${ DB_EXPORT_FILE }`
);
databaseImported = true;
} catch ( _error ) {
// noop
}
const requestContext = await request.newContext( {
baseURL: BASE_URL,
} );
console.time( timers.authentication );
await new RequestUtils( requestContext, {
user: admin,
storageStatePath: adminFile,
} ).setupRest();
console.log( '├ Pre-authenticating users…' );
await new RequestUtils( requestContext, {
user: customer,
storageStatePath: customerFile,
} ).setupRest();
console.timeEnd( timers.authentication );
const requestUtils = new RequestUtils( requestContext, {
user: admin,
storageStatePath: adminFile,
} );
await requestUtils.setupRest();
console.time( timers.attributes );
await prepareAttributes();
console.timeEnd( timers.attributes );
if ( ! databaseImported ) {
console.log( '├ Activating default theme…' );
await requestUtils.activateTheme( BLOCK_THEME_SLUG );
console.log( '├ Preparing product attributes…' );
await prepareAttributes();
}
console.log( '├ Exporting database…' );
await cli(
`npm run wp-env run tests-cli wp db export ${ DB_EXPORT_FILE }`
);
await requestContext.dispose();
console.timeEnd( timers.total );
console.timeEnd( '└ Total time' );
}
export default globalSetup;

View File

@ -1,3 +0,0 @@
/* eslint-disable no-console, @typescript-eslint/no-empty-function */
module.exports = async () => {};

View File

@ -161,12 +161,9 @@ const test = base.extend<
window.localStorage.clear();
} );
const cliOutput = await cli(
await cli(
`npm run wp-env run tests-cli wp db import ${ DB_EXPORT_FILE }`
);
if ( ! cliOutput.stdout.includes( 'Success: Imported ' ) ) {
throw new Error( `Failed to import ${ DB_EXPORT_FILE }` );
}
},
pageUtils: async ( { page }, use ) => {
await use( new PageUtils( { page } ) );

View File

@ -1,28 +0,0 @@
/**
* External dependencies
*/
import { defineConfig } from '@playwright/test';
/**
* Internal dependencies
*/
import config from './playwright.config';
export default defineConfig( {
...config,
outputDir: 'artifacts/test-results-block-theme-with-templates',
fullyParallel: true,
workers: 1,
projects: [
{
name: 'blockThemeWithTemplatesConfiguration',
testDir: '.',
testMatch: /block-theme-with-templates.setup.ts/,
},
{
name: 'blockThemeWithTemplates',
testMatch: /.*.block_theme_with_templates.spec.ts/,
dependencies: [ 'blockThemeWithTemplatesConfiguration' ],
},
],
} );

View File

@ -1,28 +0,0 @@
/**
* External dependencies
*/
import { defineConfig } from '@playwright/test';
/**
* Internal dependencies
*/
import config from './playwright.config';
export default defineConfig( {
...config,
outputDir: 'artifacts/test-results-classic-theme',
fullyParallel: false,
workers: 1,
projects: [
{
name: 'classicThemeConfiguration',
testDir: '.',
testMatch: /classic-theme.setup.ts/,
},
{
name: 'classicTheme',
testMatch: /.*.classic_theme.spec.ts/,
dependencies: [ 'classicThemeConfiguration' ],
},
],
} );

View File

@ -1,43 +1,30 @@
/**
* External dependencies
*/
import { defineConfig, PlaywrightTestConfig } from '@playwright/test';
import { BASE_URL, STORAGE_STATE_PATH } from '@woocommerce/e2e-utils';
import { fileURLToPath } from 'url';
import { BASE_URL, STORAGE_STATE_PATH } from '@woocommerce/e2e-utils';
import { PlaywrightTestConfig, defineConfig, devices } from '@playwright/test';
// eslint-disable-next-line @typescript-eslint/no-var-requires
require( 'dotenv' ).config();
interface ExtendedPlaywrightTestConfig extends PlaywrightTestConfig {
use: {
stateDir?: string;
} & PlaywrightTestConfig[ 'use' ];
}
const { CI, DEFAULT_TIMEOUT_OVERRIDE } = process.env;
const { CI, DEFAULT_TIMEOUT_OVERRIDE, E2E_MAX_FAILURES } = process.env;
const config: ExtendedPlaywrightTestConfig = {
const config: PlaywrightTestConfig = {
maxFailures: 0,
timeout: parseInt( DEFAULT_TIMEOUT_OVERRIDE || '', 10 ) || 100_000, // Defaults to 100s.
outputDir: 'artifacts/test-results',
outputDir: './artifacts/test-results',
globalSetup: fileURLToPath(
new URL( 'global-setup.ts', 'file:' + __filename ).href
),
globalTeardown: require.resolve( './global-teardown' ),
testDir: 'tests',
testDir: './tests',
retries: CI ? 2 : 0,
// We're running our tests in serial, so we only need one worker.
workers: 1,
fullyParallel: false,
// Don't report slow test "files", as we're running our tests in serial.
reportSlowTests: null,
reporter: process.env.CI
? [ [ 'github' ], [ 'list' ], [ 'html' ] ]
: 'list',
maxFailures: E2E_MAX_FAILURES ? Number( E2E_MAX_FAILURES ) : 0,
snapshotPathTemplate:
'{testDir}/{testFileDir}/__screenshots__/{arg}{testName}{ext}',
reporter: process.env.CI ? [ [ 'github' ], [ 'list' ] ] : 'list',
use: {
baseURL: BASE_URL,
screenshot: 'only-on-failure',
stateDir: 'tests/e2e/test-results/storage/',
trace: 'retain-on-failure',
video: 'on-first-retry',
viewport: { width: 1280, height: 720 },
@ -47,14 +34,8 @@ const config: ExtendedPlaywrightTestConfig = {
},
projects: [
{
name: 'blockThemeConfiguration',
testDir: '.',
testMatch: /block-theme.setup.ts/,
},
{
name: 'blockTheme',
testMatch: /.*.block_theme.spec.ts/,
dependencies: [ 'blockThemeConfiguration' ],
name: 'chromium',
use: { ...devices[ 'Desktop Chrome' ] },
},
],
};

View File

@ -1,28 +0,0 @@
/**
* External dependencies
*/
import { defineConfig } from '@playwright/test';
/**
* Internal dependencies
*/
import config from './playwright.config';
export default defineConfig( {
...config,
outputDir: 'artifacts/test-results-side-effects',
fullyParallel: false,
workers: 1,
projects: [
{
name: 'blockThemeConfiguration',
testDir: '.',
testMatch: /block-theme.setup.ts/,
},
{
name: 'blockThemeWithGlobalSideEffects',
testMatch: /.*.block_theme.side_effects.spec.ts/,
dependencies: [ 'blockThemeConfiguration' ],
},
],
} );

View File

@ -20,6 +20,7 @@ test.describe( 'Basic role-based functionality tests', () => {
test.use( {
storageState: customerFile,
} );
test( 'Load My Account page', async ( { page } ) => {
await page.goto( '/my-account' );

View File

@ -1,9 +1,14 @@
/**
* External dependencies
*/
import { CLASSIC_THEME_SLUG } from '@woocommerce/e2e-utils';
import { test, expect } from '@woocommerce/e2e-playwright-utils';
test.describe( 'Merchant → Cart', () => {
test.beforeEach( async ( { requestUtils } ) => {
await requestUtils.activateTheme( CLASSIC_THEME_SLUG );
} );
test.describe( 'in widget editor', () => {
test( "can't be inserted in a widget area", async ( {
editorUtils,

View File

@ -26,7 +26,9 @@ const test = base.extend< { checkoutPageObject: CheckoutPage } >( {
} );
test.describe( 'Shopper → Notice Templates', () => {
test.beforeEach( async ( { wpCliUtils, frontendUtils } ) => {
test.beforeEach( async ( { requestUtils, wpCliUtils, frontendUtils } ) => {
await requestUtils.activateTheme( CLASSIC_THEME_SLUG );
const cartShortcodeID = await wpCliUtils.getPostIDByTitle(
'Cart Shortcode'
);

View File

@ -69,7 +69,6 @@ test.describe( 'Shopper → Translations', () => {
const beanieAddToCartButton = page.getByLabel(
'Toevoegen aan winkelwagen: “Beanie“'
);
await beanieAddToCartButton.click();
await page.getByLabel( 'Toevoegen aan winkelwagen: “Beanie“' ).click();
@ -117,7 +116,6 @@ test.describe( 'Shopper → Translations', () => {
).toBeVisible();
await expect( page.getByText( 'Subtotaal' ) ).toBeVisible();
await expect( page.getByText( 'Verzending' ) ).toBeVisible();
await expect(

View File

@ -1,9 +1,14 @@
/**
* External dependencies
*/
import { CLASSIC_THEME_SLUG } from '@woocommerce/e2e-utils';
import { test, expect } from '@woocommerce/e2e-playwright-utils';
test.describe( 'Merchant → Checkout', () => {
test.beforeEach( async ( { requestUtils } ) => {
await requestUtils.activateTheme( CLASSIC_THEME_SLUG );
} );
test.describe( 'in widget editor', () => {
test( "can't be inserted in a widget area", async ( {
editorUtils,

View File

@ -64,16 +64,17 @@ test.describe( 'Shopper → Account (guest user)', () => {
requestUtils,
checkoutPageObject,
page,
baseURL,
} ) => {
//Get the login link from checkout page.
const loginLink = page.getByText( 'Log in.' );
const loginLinkHref = await loginLink.getAttribute( 'href' );
const loginLink = page.getByRole( 'link', { name: 'Log in.' } );
//Confirm login link is correct.
expect( loginLinkHref ).toContain(
`${ process.env.WORDPRESS_BASE_URL }/my-account/?redirect_to`
await expect( loginLink ).toHaveAttribute(
'href',
baseURL +
'/my-account/?redirect_to=' +
encodeURIComponent( baseURL + '/checkout/' )
);
expect( loginLinkHref ).toContain( `checkout` );
await requestUtils.rest( {
method: 'PUT',
@ -85,7 +86,8 @@ test.describe( 'Shopper → Account (guest user)', () => {
const createAccount = page.getByLabel( 'Create an account?' );
await createAccount.check();
const testEmail = `test${ Math.random() * 10 }@example.com`;
const testEmail = `test-${ Date.now() }@example.com`;
await checkoutPageObject.fillInCheckoutWithTestData( {
email: testEmail,
} );

View File

@ -1,94 +0,0 @@
/**
* External dependencies
*/
import { test, expect } from '@woocommerce/e2e-playwright-utils';
/**
* Internal dependencies
*/
import { REGULAR_PRICED_PRODUCT_NAME } from '../checkout/constants';
test.describe( 'Merchant → Local Pickup Settings', () => {
test.beforeEach( async ( { localPickupUtils } ) => {
await localPickupUtils.deleteLocations();
await localPickupUtils.addPickupLocation( {
location: {
name: 'Automattic, Inc.',
address: '60 29th Street, Suite 343',
city: 'San Francisco',
postcode: '94110',
state: 'US:CA',
details: 'American entity',
},
} );
await localPickupUtils.disableLocalPickupCosts();
await localPickupUtils.enableLocalPickup();
} );
test( 'Updating the title in WC Settings updates the local pickup text in the block and vice/versa', async ( {
page,
localPickupUtils,
admin,
editor,
editorUtils,
frontendUtils,
} ) => {
// First update the title via the site editor then check the local pickup settings.
await admin.visitSiteEditor( {
postId: 'woocommerce/woocommerce//page-checkout',
postType: 'wp_template',
} );
await editorUtils.enterEditMode();
const block = await editorUtils.getBlockByName(
'woocommerce/checkout-shipping-method-block'
);
await editor.selectBlocks( block );
const fakeInput = editor.canvas.getByLabel( 'Pickup', { exact: true } );
await fakeInput.click();
const isMacOS = process.platform === 'darwin'; // darwin is macOS
// eslint-disable-next-line playwright/no-conditional-in-test
if ( isMacOS ) {
await fakeInput.press( 'Meta+a' );
} else {
await fakeInput.press( 'Control+a' );
}
await fakeInput.press( 'Backspace' );
await fakeInput.pressSequentially( 'This is a test' );
await editor.canvas.getByText( 'This is a test' ).isVisible();
await editor.saveSiteEditorEntities();
// Now check if it's visible in the local pickup settings.
await localPickupUtils.openLocalPickupSettings();
await expect( page.getByLabel( 'Title' ) ).toHaveValue(
'This is a test'
);
// Now update the title via local pickup settings and check it reflects in the site editor and front end.
await localPickupUtils.setLocalPickupTitle(
'Edited from settings page'
);
await localPickupUtils.saveLocalPickupSettings();
await admin.visitSiteEditor( {
postId: 'woocommerce/woocommerce//page-checkout',
postType: 'wp_template',
} );
await editorUtils.enterEditMode();
await expect(
editor.canvas.getByText( 'Edited from settings page' )
).toBeVisible();
await frontendUtils.emptyCart();
await frontendUtils.goToShop();
await frontendUtils.addToCart( REGULAR_PRICED_PRODUCT_NAME );
await frontendUtils.goToCheckout();
await expect(
page.getByText( 'Edited from settings page' )
).toBeVisible();
} );
} );

View File

@ -3,9 +3,13 @@
*/
import { test, expect } from '@woocommerce/e2e-playwright-utils';
/**
* Internal dependencies
*/
import { REGULAR_PRICED_PRODUCT_NAME } from '../checkout/constants';
test.describe( 'Merchant → Local Pickup Settings', () => {
test.beforeEach( async ( { localPickupUtils } ) => {
await localPickupUtils.deleteLocations();
await localPickupUtils.disableLocalPickupCosts();
await localPickupUtils.enableLocalPickup();
} );
@ -96,7 +100,6 @@ test.describe( 'Merchant → Local Pickup Settings', () => {
localPickupUtils,
} ) => {
await localPickupUtils.addPickupLocation( {
page,
location: {
name: 'Automattic, Inc.',
address: '60 29th Street, Suite 343',
@ -116,7 +119,6 @@ test.describe( 'Merchant → Local Pickup Settings', () => {
test( 'user can edit a location', async ( { page, localPickupUtils } ) => {
await localPickupUtils.addPickupLocation( {
page,
location: {
name: 'Automattic, Inc.',
address: '60 29th Street, Suite 343',
@ -134,7 +136,6 @@ test.describe( 'Merchant → Local Pickup Settings', () => {
).toBeVisible();
await localPickupUtils.editPickupLocation( {
page,
location: {
name: 'Ministry of Automattic Limited',
address: '100 New Bridge Street',
@ -157,7 +158,6 @@ test.describe( 'Merchant → Local Pickup Settings', () => {
localPickupUtils,
} ) => {
await localPickupUtils.addPickupLocation( {
page,
location: {
name: 'Ausomattic Pty Ltd',
address:
@ -183,4 +183,64 @@ test.describe( 'Merchant → Local Pickup Settings', () => {
} )
).toBeVisible();
} );
test( 'updating the title in WC Settings updates the local pickup text in the block and vice/versa', async ( {
page,
localPickupUtils,
admin,
editor,
editorUtils,
frontendUtils,
} ) => {
// First update the title via the site editor then check the local pickup settings.
await admin.visitSiteEditor( {
postId: 'woocommerce/woocommerce//page-checkout',
postType: 'wp_template',
} );
await editorUtils.enterEditMode();
const block = await editorUtils.getBlockByName(
'woocommerce/checkout-shipping-method-block'
);
await editor.selectBlocks( block );
const fakeInput = editor.canvas.getByLabel( 'Pickup', { exact: true } );
await fakeInput.focus();
await fakeInput.dblclick(); // Select all text.
await fakeInput.pressSequentially( 'This is a test' ); // We can't use locator.fill() because it's not a valid input element.
await editor.canvas.getByText( 'This is a test' ).isVisible();
await editor.saveSiteEditorEntities();
// Now check if it's visible in the local pickup settings.
await localPickupUtils.openLocalPickupSettings();
await expect( page.getByLabel( 'Title' ) ).toHaveValue(
'This is a test'
);
// Now update the title via local pickup settings and check it reflects in the site editor and front end.
await localPickupUtils.setLocalPickupTitle(
'Edited from settings page'
);
await localPickupUtils.saveLocalPickupSettings();
await admin.visitSiteEditor( {
postId: 'woocommerce/woocommerce//page-checkout',
postType: 'wp_template',
} );
await editorUtils.enterEditMode();
await expect(
editor.canvas.getByText( 'Edited from settings page' )
).toBeVisible();
await frontendUtils.emptyCart();
await frontendUtils.goToShop();
await frontendUtils.addToCart( REGULAR_PRICED_PRODUCT_NAME );
await frontendUtils.goToCheckout();
await expect(
page.getByText( 'Edited from settings page' )
).toBeVisible();
} );
} );

View File

@ -1,6 +1,7 @@
/**
* External dependencies
*/
import { CLASSIC_THEME_SLUG } from '@woocommerce/e2e-utils';
import { expect, test } from '@woocommerce/e2e-playwright-utils';
import { BlockData } from '@woocommerce/e2e-types';
@ -15,6 +16,10 @@ const blockData: BlockData = {
};
test.describe( 'Merchant → Mini Cart', () => {
test.beforeEach( async ( { requestUtils } ) => {
await requestUtils.activateTheme( CLASSIC_THEME_SLUG );
} );
test.describe( 'in widget editor', () => {
test( 'can be inserted in a widget area', async ( { editorUtils } ) => {
await editorUtils.openWidgetEditor();

View File

@ -1,6 +1,7 @@
/**
* External dependencies
*/
import { CLASSIC_THEME_SLUG } from '@woocommerce/e2e-utils';
import { expect, test as base } from '@woocommerce/e2e-playwright-utils';
/**
@ -25,7 +26,8 @@ const test = base.extend< { productCollectionPage: ProductCollectionPage } >( {
},
} );
test.describe( `${ blockData.name } Block`, () => {
test.beforeEach( async ( { page } ) => {
test.beforeEach( async ( { page, requestUtils } ) => {
await requestUtils.activateTheme( CLASSIC_THEME_SLUG );
await page.goto( '/product-collection/' );
} );

View File

@ -5,10 +5,6 @@ import { test, expect } from '@woocommerce/e2e-playwright-utils';
import { cli } from '@woocommerce/e2e-utils';
test.describe( 'Legacy templates', () => {
test.beforeEach( async ( { requestUtils } ) => {
await requestUtils.deleteAllTemplates( 'wp_template' );
} );
test( 'woocommerce//* slug is supported', async ( {
admin,
page,
@ -48,15 +44,13 @@ test.describe( 'Legacy templates', () => {
} );
await test.step( 'Update created term to legacy format in the DB', async () => {
const cliOutput = await cli(
await cli(
`npm run wp-env run tests-cli -- \
wp term update wp_theme woocommerce-woocommerce \
--by="slug" \
--name="woocommerce" \
--slug="woocommerce"`
);
expect( cliOutput.stdout ).toContain( 'Success: Term updated.' );
} );
await test.step( 'Verify the template can be edited via a legacy ID ', async () => {
@ -88,17 +82,5 @@ test.describe( 'Legacy templates', () => {
await expect( page.getByText( template.customText ) ).toBeVisible();
} );
await test.step( 'Revert term update', async () => {
const cliOutput = await cli(
`npm run wp-env run tests-cli -- \
wp term update wp_theme woocommerce \
--by="slug" \
--name="woocommerce/woocommerce" \
--slug="woocommerce-woocommerce"`
);
expect( cliOutput.stdout ).toContain( 'Success: Term updated.' );
} );
} );
} );

View File

@ -1,6 +1,7 @@
/**
* External dependencies
*/
import { BLOCK_THEME_WITH_TEMPLATES_SLUG } from '@woocommerce/e2e-utils';
import { test, expect } from '@woocommerce/e2e-playwright-utils';
/**
@ -19,6 +20,10 @@ const userText = 'Hello World in the Belt template';
const themeTemplateText = 'Single Product Belt template loaded from theme';
test.describe( 'Single Product Template', () => {
test.beforeEach( async ( { requestUtils } ) => {
await requestUtils.activateTheme( BLOCK_THEME_WITH_TEMPLATES_SLUG );
} );
test( 'loads the theme template for a specific product using the product slug and it can be customized', async ( {
admin,
editor,

View File

@ -1,89 +0,0 @@
/**
* External dependencies
*/
import { test, expect } from '@woocommerce/e2e-playwright-utils';
import {
BLOCK_THEME_SLUG,
BLOCK_THEME_WITH_TEMPLATES_SLUG,
} from '@woocommerce/e2e-utils';
/**
* Internal dependencies
*/
import { CUSTOMIZABLE_WC_TEMPLATES } from './constants';
const testToRun = CUSTOMIZABLE_WC_TEMPLATES.filter(
( data ) => data.canBeOverriddenByThemes
);
for ( const testData of testToRun ) {
const userText = `Hello World in the ${ testData.templateName } template`;
const woocommerceTemplateUserText = `Hello World in the WooCommerce ${ testData.templateName } template`;
test.describe( `${ testData.templateName } template`, () => {
test( `user-modified ${ testData.templateName } template based on the theme template has priority over the user-modified template based on the default WooCommerce template`, async ( {
page,
admin,
editor,
requestUtils,
editorUtils,
frontendUtils,
} ) => {
// Edit the WooCommerce default template
await editorUtils.visitTemplateEditor(
testData.templateName,
testData.templateType
);
await editor.insertBlock( {
name: 'core/paragraph',
attributes: { content: woocommerceTemplateUserText },
} );
await editor.saveSiteEditorEntities();
await requestUtils.activateTheme( BLOCK_THEME_WITH_TEMPLATES_SLUG );
// Edit the theme template. The theme template is not
// directly available from the UI, because the customized
// one takes priority, so we go directly to its URL.
await admin.visitSiteEditor( {
postId: `${ BLOCK_THEME_WITH_TEMPLATES_SLUG }//${ testData.templatePath }`,
postType: testData.templateType,
} );
await editorUtils.enterEditMode();
await editorUtils.waitForSiteEditorFinishLoading();
await editorUtils.editor.insertBlock( {
name: 'core/paragraph',
attributes: { content: userText },
} );
await editor.saveSiteEditorEntities();
// Verify the template is the one modified by the user based on the theme.
await testData.visitPage( { frontendUtils, page } );
await expect( page.getByText( userText ).first() ).toBeVisible();
await expect(
page.getByText( woocommerceTemplateUserText )
).toHaveCount( 0 );
// Revert edition and verify the user-modified WC template is used.
// Note: we need to revert it from the admin (instead of calling
// `deleteAllTemplates()`). This way, we verify there are no
// duplicate templates with the same name.
// See: https://github.com/woocommerce/woocommerce/issues/42220
await admin.visitSiteEditor( {
path: `/${ testData.templateType }/all`,
} );
await editorUtils.revertTemplateCustomizations(
testData.templateName
);
await testData.visitPage( { frontendUtils, page } );
await expect(
page.getByText( woocommerceTemplateUserText ).first()
).toBeVisible();
await expect( page.getByText( userText ) ).toHaveCount( 0 );
await requestUtils.activateTheme( BLOCK_THEME_SLUG );
} );
} );
}

View File

@ -2,81 +2,54 @@
* External dependencies
*/
import { test, expect } from '@woocommerce/e2e-playwright-utils';
import {
BLOCK_THEME_SLUG,
BLOCK_THEME_WITH_TEMPLATES_SLUG,
} from '@woocommerce/e2e-utils';
/**
* Internal dependencies
*/
import { CUSTOMIZABLE_WC_TEMPLATES } from './constants';
CUSTOMIZABLE_WC_TEMPLATES.forEach( ( testData ) => {
const userText = `Hello World in the ${ testData.templateName } template`;
const fallbackTemplateUserText = `Hello World in the fallback ${ testData.templateName } template`;
const templateTypeName =
testData.templateType === 'wp_template' ? 'template' : 'template part';
test.describe( 'Template customization', () => {
CUSTOMIZABLE_WC_TEMPLATES.forEach( ( testData ) => {
const userText = `Hello World in the ${ testData.templateName } template`;
const fallbackTemplateUserText = `Hello World in the fallback ${ testData.templateName } template`;
const templateTypeName =
testData.templateType === 'wp_template'
? 'template'
: 'template part';
test.describe( `${ testData.templateName } template`, () => {
test( 'can be modified and reverted', async ( {
admin,
frontendUtils,
editor,
editorUtils,
page,
} ) => {
// Verify the template can be edited.
await editorUtils.visitTemplateEditor(
testData.templateName,
testData.templateType
);
await editor.insertBlock( {
name: 'core/paragraph',
attributes: { content: userText },
} );
await editor.saveSiteEditorEntities();
// Verify template name didn't change.
// See: https://github.com/woocommerce/woocommerce/issues/42221
await expect(
page.getByRole( 'heading', {
name: `Editing ${ templateTypeName }: ${ testData.templateName }`,
} )
).toBeVisible();
await testData.visitPage( { frontendUtils, page } );
await expect( page.getByText( userText ).first() ).toBeVisible();
// Verify the edition can be reverted.
await admin.visitSiteEditor( {
path: `/${ testData.templateType }/all`,
} );
await editorUtils.revertTemplateCustomizations(
testData.templateName
);
await testData.visitPage( { frontendUtils, page } );
await expect( page.getByText( userText ) ).toHaveCount( 0 );
} );
if ( testData.fallbackTemplate ) {
test( `defaults to the ${ testData.fallbackTemplate.templateName } template`, async ( {
test.describe( `${ testData.templateName } template`, () => {
test( 'can be modified and reverted', async ( {
admin,
frontendUtils,
editor,
editorUtils,
page,
} ) => {
// Edit fallback template and verify changes are visible.
// Verify the template can be edited.
await editorUtils.visitTemplateEditor(
testData.fallbackTemplate?.templateName || '',
testData.templateName,
testData.templateType
);
await editor.insertBlock( {
name: 'core/paragraph',
attributes: {
content: fallbackTemplateUserText,
},
attributes: { content: userText },
} );
await editor.saveSiteEditorEntities();
// Verify template name didn't change.
// See: https://github.com/woocommerce/woocommerce/issues/42221
await expect(
page.getByRole( 'heading', {
name: `Editing ${ templateTypeName }: ${ testData.templateName }`,
} )
).toBeVisible();
await testData.visitPage( { frontendUtils, page } );
await expect(
page.getByText( fallbackTemplateUserText ).first()
page.getByText( userText ).first()
).toBeVisible();
// Verify the edition can be reverted.
@ -84,13 +57,130 @@ CUSTOMIZABLE_WC_TEMPLATES.forEach( ( testData ) => {
path: `/${ testData.templateType }/all`,
} );
await editorUtils.revertTemplateCustomizations(
testData.fallbackTemplate?.templateName || ''
testData.templateName
);
await testData.visitPage( { frontendUtils, page } );
await expect(
page.getByText( fallbackTemplateUserText )
).toHaveCount( 0 );
await expect( page.getByText( userText ) ).toHaveCount( 0 );
} );
}
if ( testData.fallbackTemplate ) {
test( `defaults to the ${ testData.fallbackTemplate.templateName } template`, async ( {
admin,
frontendUtils,
editor,
editorUtils,
page,
} ) => {
// Edit fallback template and verify changes are visible.
await editorUtils.visitTemplateEditor(
testData.fallbackTemplate?.templateName || '',
testData.templateType
);
await editor.insertBlock( {
name: 'core/paragraph',
attributes: {
content: fallbackTemplateUserText,
},
} );
await editor.saveSiteEditorEntities();
await testData.visitPage( { frontendUtils, page } );
await expect(
page.getByText( fallbackTemplateUserText ).first()
).toBeVisible();
// Verify the edition can be reverted.
await admin.visitSiteEditor( {
path: `/${ testData.templateType }/all`,
} );
await editorUtils.revertTemplateCustomizations(
testData.fallbackTemplate?.templateName || ''
);
await testData.visitPage( { frontendUtils, page } );
await expect(
page.getByText( fallbackTemplateUserText )
).toHaveCount( 0 );
} );
}
} );
} );
const testToRun = CUSTOMIZABLE_WC_TEMPLATES.filter(
( data ) => data.canBeOverriddenByThemes
);
for ( const testData of testToRun ) {
const userText = `Hello World in the ${ testData.templateName } template`;
const woocommerceTemplateUserText = `Hello World in the WooCommerce ${ testData.templateName } template`;
test.describe( `${ testData.templateName } template`, () => {
test( `user-modified ${ testData.templateName } template based on the theme template has priority over the user-modified template based on the default WooCommerce template`, async ( {
page,
admin,
editor,
requestUtils,
editorUtils,
frontendUtils,
} ) => {
// Edit the WooCommerce default template
await editorUtils.visitTemplateEditor(
testData.templateName,
testData.templateType
);
await editor.insertBlock( {
name: 'core/paragraph',
attributes: { content: woocommerceTemplateUserText },
} );
await editor.saveSiteEditorEntities();
await requestUtils.activateTheme(
BLOCK_THEME_WITH_TEMPLATES_SLUG
);
// Edit the theme template. The theme template is not
// directly available from the UI, because the customized
// one takes priority, so we go directly to its URL.
await admin.visitSiteEditor( {
postId: `${ BLOCK_THEME_WITH_TEMPLATES_SLUG }//${ testData.templatePath }`,
postType: testData.templateType,
} );
await editorUtils.enterEditMode();
await editorUtils.waitForSiteEditorFinishLoading();
await editorUtils.editor.insertBlock( {
name: 'core/paragraph',
attributes: { content: userText },
} );
await editor.saveSiteEditorEntities();
// Verify the template is the one modified by the user based on the theme.
await testData.visitPage( { frontendUtils, page } );
await expect(
page.getByText( userText ).first()
).toBeVisible();
await expect(
page.getByText( woocommerceTemplateUserText )
).toHaveCount( 0 );
// Revert edition and verify the user-modified WC template is used.
// Note: we need to revert it from the admin (instead of calling
// `deleteAllTemplates()`). This way, we verify there are no
// duplicate templates with the same name.
// See: https://github.com/woocommerce/woocommerce/issues/42220
await admin.visitSiteEditor( {
path: `/${ testData.templateType }/all`,
} );
await editorUtils.revertTemplateCustomizations(
testData.templateName
);
await testData.visitPage( { frontendUtils, page } );
await expect(
page.getByText( woocommerceTemplateUserText ).first()
).toBeVisible();
await expect( page.getByText( userText ) ).toHaveCount( 0 );
await requestUtils.activateTheme( BLOCK_THEME_SLUG );
} );
} );
}
} );

View File

@ -1,6 +1,7 @@
/**
* External dependencies
*/
import { BLOCK_THEME_WITH_TEMPLATES_SLUG } from '@woocommerce/e2e-utils';
import { test, expect } from '@woocommerce/e2e-playwright-utils';
/**
@ -8,97 +9,107 @@ import { test, expect } from '@woocommerce/e2e-playwright-utils';
*/
import { CUSTOMIZABLE_WC_TEMPLATES } from './constants';
CUSTOMIZABLE_WC_TEMPLATES.forEach( ( testData ) => {
if ( ! testData.canBeOverriddenByThemes ) {
return;
}
const userText = `Hello World in the ${ testData.templateName } template`;
const fallbackTemplateUserText = `Hello World in the fallback ${ testData.templateName } template`;
const templateTypeName =
testData.templateType === 'wp_template' ? 'template' : 'template part';
test.describe( 'Template customization', () => {
test.beforeEach( async ( { requestUtils } ) => {
await requestUtils.activateTheme( BLOCK_THEME_WITH_TEMPLATES_SLUG );
} );
test.describe( `${ testData.templateName } template`, () => {
test( "theme template has priority over WooCommerce's and can be modified", async ( {
admin,
editor,
editorUtils,
frontendUtils,
page,
} ) => {
// Edit the theme template.
await editorUtils.visitTemplateEditor(
testData.templateName,
testData.templateType
);
await editor.insertBlock( {
name: 'core/paragraph',
attributes: { content: userText },
} );
await editor.saveSiteEditorEntities();
// Verify template name didn't change.
// See: https://github.com/woocommerce/woocommerce/issues/42221
await expect(
page.getByRole( 'heading', {
name: `Editing ${ templateTypeName }: ${ testData.templateName }`,
} )
).toBeVisible();
CUSTOMIZABLE_WC_TEMPLATES.forEach( ( testData ) => {
if ( ! testData.canBeOverriddenByThemes ) {
return;
}
const userText = `Hello World in the ${ testData.templateName } template`;
const fallbackTemplateUserText = `Hello World in the fallback ${ testData.templateName } template`;
const templateTypeName =
testData.templateType === 'wp_template'
? 'template'
: 'template part';
// Verify the template is the one modified by the user.
await testData.visitPage( { frontendUtils, page } );
await expect( page.getByText( userText ).first() ).toBeVisible();
// Revert edition and verify the template from the theme is used.
await admin.visitSiteEditor( {
path: `/${ testData.templateType }/all`,
} );
await editorUtils.revertTemplateCustomizations(
testData.templateName
);
await testData.visitPage( { frontendUtils, page } );
await expect(
page
.getByText(
`${ testData.templateName } template loaded from theme`
)
.first()
).toBeVisible();
await expect( page.getByText( userText ) ).toHaveCount( 0 );
} );
if ( testData.fallbackTemplate ) {
test( `theme template has priority over user-modified ${ testData.fallbackTemplate.templateName } template`, async ( {
test.describe( `${ testData.templateName } template`, () => {
test( "theme template has priority over WooCommerce's and can be modified", async ( {
admin,
frontendUtils,
editor,
editorUtils,
frontendUtils,
page,
} ) => {
// Edit default template and verify changes are not visible, as the theme template has priority.
// Edit the theme template.
await editorUtils.visitTemplateEditor(
testData.fallbackTemplate?.templateName || '',
testData.templateName,
testData.templateType
);
await editor.insertBlock( {
name: 'core/paragraph',
attributes: {
content: fallbackTemplateUserText,
},
attributes: { content: userText },
} );
await editor.saveSiteEditorEntities();
// Verify template name didn't change.
// See: https://github.com/woocommerce/woocommerce/issues/42221
await expect(
page.getByRole( 'heading', {
name: `Editing ${ templateTypeName }: ${ testData.templateName }`,
} )
).toBeVisible();
// Verify the template is the one modified by the user.
await testData.visitPage( { frontendUtils, page } );
await expect(
page.getByText( fallbackTemplateUserText )
).toHaveCount( 0 );
page.getByText( userText ).first()
).toBeVisible();
// Revert the edit.
// Revert edition and verify the template from the theme is used.
await admin.visitSiteEditor( {
path: `/${ testData.templateType }/all`,
} );
await editorUtils.revertTemplateCustomizations(
testData.fallbackTemplate?.templateName || ''
testData.templateName
);
await testData.visitPage( { frontendUtils, page } );
await expect(
page
.getByText(
`${ testData.templateName } template loaded from theme`
)
.first()
).toBeVisible();
await expect( page.getByText( userText ) ).toHaveCount( 0 );
} );
}
if ( testData.fallbackTemplate ) {
test( `theme template has priority over user-modified ${ testData.fallbackTemplate.templateName } template`, async ( {
admin,
frontendUtils,
editor,
editorUtils,
page,
} ) => {
// Edit default template and verify changes are not visible, as the theme template has priority.
await editorUtils.visitTemplateEditor(
testData.fallbackTemplate?.templateName || '',
testData.templateType
);
await editor.insertBlock( {
name: 'core/paragraph',
attributes: {
content: fallbackTemplateUserText,
},
} );
await editor.saveSiteEditorEntities();
await testData.visitPage( { frontendUtils, page } );
await expect(
page.getByText( fallbackTemplateUserText )
).toHaveCount( 0 );
// Revert the edit.
await admin.visitSiteEditor( {
path: `/${ testData.templateType }/all`,
} );
await editorUtils.revertTemplateCustomizations(
testData.fallbackTemplate?.templateName || ''
);
} );
}
} );
} );
} );

View File

@ -1,25 +1,35 @@
/**
* External dependencies
*/
import { ExecException, exec } from 'child_process';
import { exec, ExecException } from 'child_process';
export function cli(
cmd: string,
args = []
): Promise< {
code: number;
error: ExecException | null;
interface ExecResult {
stdout: string;
stderr: string;
} > {
return new Promise( ( resolve ) => {
exec( `${ cmd } ${ args.join( ' ' ) }`, ( error, stdout, stderr ) => {
resolve( {
code: error && error.code ? error.code : 0,
error,
stdout,
stderr,
} );
} );
}
interface ExecError extends ExecResult {
error: ExecException | null;
}
export function cli( command: string ): Promise< ExecResult > {
return new Promise( ( resolve, reject ) => {
exec(
command,
( error: ExecException | null, stdout: string, stderr: string ) => {
if ( error ) {
reject( {
error,
stdout,
stderr,
} as ExecError );
} else {
resolve( {
stdout,
stderr,
} as ExecResult );
}
}
);
} );
}

View File

@ -0,0 +1,4 @@
Significance: patch
Type: dev
Blocks E2E: Refactor Playwright configs and CI workflow