Skip to content
Open

Release #4587

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
Expand Up @@ -150,5 +150,7 @@ jobs:
if: failure()
uses: actions/upload-artifact@v4
with:
name: traces-${{ matrix.specs }}-${{ matrix.envs }}
path: ./test-results/**
if-no-files-found: ignore
retention-days: 1
7 changes: 5 additions & 2 deletions bin/ss-pages.test.stub
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ describe( 'Starter Sites VR - {{site}}', () => {
let frontpage = "{{site}}"
cy.visit(frontpage );
cy.captureDocument();
cy.get( '#nv-primary-navigation-main' ).then( $headerMenu => {
cy.get( '[id^="nv-primary-navigation"]' ).then( $headerMenu => {
[ ...$headerMenu.find( '.menu-item a' ) ].forEach( $url => {
let url = $url.href;
if(url.includes("#")){
Expand All @@ -13,8 +13,11 @@ describe( 'Starter Sites VR - {{site}}', () => {
if(frontpage.replace(/\/*$/, "") === url.replace(/\/*$/, "")){
return;
}
if(pages.includes(url)){
return;
}

pages.push( $url.href );
pages.push( url );
} );
} );
} );
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"hfg_footer_layout_v2": "{\"desktop\":{\"top\":{\"left\":[],\"c-left\":[],\"center\":[],\"c-right\":[],\"right\":[]},\"main\":{\"left\":[],\"c-left\":[],\"center\":[],\"c-right\":[],\"right\":[]},\"bottom\":{\"left\":[],\"c-left\":[],\"center\":[{\"id\":\"footer-menu\"}],\"c-right\":[],\"right\":[]}},\"mobile\":{\"top\":{\"left\":[],\"c-left\":[],\"center\":[{\"id\":\"footer-menu\"}],\"c-right\":[],\"right\":[]},\"main\":{\"left\":[],\"c-left\":[],\"center\":[],\"c-right\":[],\"right\":[]},\"bottom\":{\"left\":[],\"c-left\":[],\"center\":[],\"c-right\":[],\"right\":[]}}}",
"nav_menu_locations[footer]": 177
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"hfg_header_layout_v2": "{\"desktop\":{\"top\":{\"left\":[],\"c-left\":[],\"center\":[],\"c-right\":[],\"right\":[]},\"main\":{\"left\":[{\"id\":\"logo\"}],\"c-left\":[],\"center\":[],\"c-right\":[],\"right\":[{\"id\":\"primary-menu\"}]},\"bottom\":{\"left\":[],\"c-left\":[],\"center\":[],\"c-right\":[],\"right\":[]}},\"mobile\":{\"top\":{\"left\":[],\"c-left\":[],\"center\":[],\"c-right\":[],\"right\":[]},\"main\":{\"left\":[{\"id\":\"logo\"}],\"c-left\":[],\"center\":[],\"c-right\":[],\"right\":[{\"id\":\"primary-menu\"},{\"id\":\"nav-icon\"}]},\"bottom\":{\"left\":[],\"c-left\":[],\"center\":[],\"c-right\":[],\"right\":[]},\"sidebar\":[{\"id\":\"primary-menu\"}]}}",
"nav_menu_locations[primary]": 177
}
15 changes: 12 additions & 3 deletions e2e-tests/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import { defineConfig, devices } from '@playwright/test';

// Plain `|| fallback` would discard an explicit 0.
const envInt = (name: string, fallback: number) => {
const parsed = parseInt(process.env[name] || '', 10);
return Number.isNaN(parsed) ? fallback : parsed;
};

export default defineConfig({
reporter: process.env.CI ? 'github' : 'list',
forbidOnly: !!process.env.CI,
workers: process.env.CI ? 6 : undefined,
retries: 0,
timeout: parseInt(process.env.TIMEOUT || '', 10) || 150_000, // Defaults to 100 seconds.
// Same values locally and in CI, so a race reproduces in both.
workers: envInt('PW_WORKERS', 2),
retries: envInt('PW_RETRIES', 2),
timeout: envInt('TIMEOUT', 150_000), // Defaults to 100 seconds.
fullyParallel: true,
projects: [
// Setup project
Expand All @@ -26,5 +33,7 @@ export default defineConfig({
headless: true,
ignoreHTTPSErrors: true,
trace: 'retain-on-failure',
actionTimeout: 20_000,
navigationTimeout: 45_000,
},
});
5 changes: 4 additions & 1 deletion e2e-tests/specs/accessibility/aria.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@ const runMenuARIATest = (deviceType = 'mobile') => {
).toHaveAttribute('aria-expanded', 'true');

// Close the menu from the overlay. Check ARIA attribute for expanded state is false when menu is closed.
// The overlay spans the viewport and the open sidebar covers its centre, so
// a real click there lands on a menu link and navigates away. Dispatching
// hits the same handler without depending on which side the sidebar is on.
await page
.locator('.header-menu-sidebar-overlay')
.click({ force: true });
.dispatchEvent('click');
await expect(
page.getByRole('button', { name: 'Navigation Menu' })
).toHaveAttribute('aria-expanded', 'false');
Expand Down
33 changes: 28 additions & 5 deletions e2e-tests/specs/admin/tpc-notice-install.spec.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,36 @@
import { test, expect } from '@playwright/test';
import { visitAdminPage } from '../../utils';
import { setCustomizeSettings, visitAdminPage } from '../../utils';

const TPC_PLUGIN =
'templates-patterns-collection/templates-patterns-collection';
const TEST_NAME = 'tpcNotice';

test.describe('Dashboard Notice', () => {
test.beforeAll(async ({ request, baseURL }) => {
const endpoint = `${baseURL}/wp-json/wp/v2/plugins/${TPC_PLUGIN}`;

await request
.put(endpoint, { data: { status: 'inactive' } })
.catch(() => null);
await request.delete(endpoint).catch(() => null);

// Overridden per-request via ?test_name=, not written to the options table.
await setCustomizeSettings(
TEST_NAME,
{
options: {
neve_notice_dismissed: 'no',
neve_install: Math.floor(Date.now() / 1000),
},
},
{ request, baseURL }
);
});

test('Starter Sites Plugin install from Dashboard Notice', async ({
page,
}) => {
await visitAdminPage(page, 'index.php', '');
await visitAdminPage(page, 'index.php', `test_name=${TEST_NAME}`);

await expect(page).toHaveURL(/wp-admin\/index.php/);

Expand All @@ -25,9 +50,7 @@ test.describe('Dashboard Notice', () => {
);

// Welcome screen
await expect(page.locator('h1')).toContainText(
'Choose a design'
);
await expect(page.locator('h1')).toContainText('Choose a design');

const categories = await page.locator('.ob-cat-wrap .cat');
await expect(categories).toContainText([
Expand Down
34 changes: 30 additions & 4 deletions e2e-tests/specs/customizer/general/custom-global-colors.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,37 @@ test.describe('Custom Global Color Control', () => {
);
await clearWelcome(page);

await page.locator('[aria-label="Document Overview"]').click();
await page.locator('.block-editor-list-view-leaf').first().click();
await page.waitForFunction(() =>
(
window.wp?.data?.select('core/block-editor')?.getSettings()
?.colors || []
).some((color: { slug: string }) => color.slug === 'custom-1')
);

// Wait for the parsed block list too, so the update below has a target.
await page.waitForFunction(
() =>
(
window.wp?.data?.select('core/block-editor')?.getBlocks() ||
[]
).length > 0
);

await page.getByRole('button', { name: 'Background' }).click();
await page.getByRole('option', { name: 'Custom 1' }).click();
// Clicking the palette swatch toggles, so a block left coloured by a
// previous run would be cleared rather than set.
await page.evaluate(() => {
const { data } = window.wp;
const [block] = data.select('core/block-editor').getBlocks();
data.dispatch('core/block-editor').updateBlockAttributes(
block.clientId,
{ backgroundColor: 'custom-1' }
);
});
await page.waitForFunction(
() =>
window.wp?.data?.select('core/block-editor')?.getBlocks()?.[0]
?.attributes?.backgroundColor === 'custom-1'
);

await savePost(page);
});
Expand Down
153 changes: 151 additions & 2 deletions e2e-tests/specs/customizer/hfg/hfg-footer-menu-component.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
import { test, expect, Page, Locator } from '@playwright/test';
import { setCustomizeSettings } from '../../../utils';
import { test, expect, Page, Locator, Frame } from '@playwright/test';
import { loginWithRequest, setCustomizeSettings } from '../../../utils';
import data from '../../../fixtures/customizer/hfg/footer-menu-setup.json';
import menuData from '../../../fixtures/customizer/hfg/footer-menu-both-devices-setup.json';

const PREVIEW_FRAME = 'iframe[name="customize-preview-0"]';

/**
* Collect the ids of every footer menu list in a document.
*
* @param {Frame} frame Frame to read from.
*/
const footerMenuIds = (frame: Frame): Promise<string[]> =>
frame.evaluate(() =>
Array.from(document.querySelectorAll('.hfg_footer ul.footer-menu')).map(
(el) => el.id
)
);

test.describe('Footer Menu component', function () {
let page: Page;
Expand Down Expand Up @@ -35,3 +50,137 @@ test.describe('Footer Menu component', function () {
}
});
});

test.describe(
'Footer Menu component in both desktop and mobile layouts',
function () {
test.beforeAll(async ({ request, baseURL }) => {
await setCustomizeSettings('hfgFooterMenuBothDevices', menuData, {
request,
baseURL,
});
});

test('Both layout variants are rendered', async ({ page }) => {
await page.goto('/?test_name=hfgFooterMenuBothDevices');

await expect(
page.locator(
'.hfg_footer .footer--row[data-show-on="desktop"] .nav-menu-footer'
)
).toHaveCount(1);

await expect(
page.locator(
'.hfg_footer .footer--row[data-show-on="mobile"] .nav-menu-footer'
)
).toHaveCount(1);
});

test('Menu IDs are unique across the whole document', async ({
page,
}) => {
await page.goto('/?test_name=hfgFooterMenuBothDevices');

await expect(page.locator('ul#footer-menu')).toHaveCount(0);
await expect(
page.locator('ul#footer-menu-desktop-bottom')
).toHaveCount(1);
await expect(page.locator('ul#footer-menu-mobile-top')).toHaveCount(
1
);

const duplicated = await page.evaluate(() => {
const ids = Array.from(
document.querySelectorAll('.hfg_footer [id]')
).map((el) => el.id);

return ids.filter((id, index) => ids.indexOf(id) !== index);
});

expect(duplicated).toEqual([]);
});

test('The footer-menu class is kept for styling', async ({ page }) => {
await page.goto('/?test_name=hfgFooterMenuBothDevices');

await expect(
page.locator('.hfg_footer ul.footer-menu')
).toHaveCount(2);
});

test('Menu IDs survive a selective refresh of the component', async ({
page,
}) => {
const previewUrl = '/?test_name=hfgFooterMenuBothDevices';
await loginWithRequest(
'/wp-admin/customize.php?url=' + encodeURIComponent(previewUrl),
page
);

await page.waitForSelector('.wp-full-overlay-sidebar', {
state: 'visible',
});

const preview = page.frameLocator(PREVIEW_FRAME);
await preview
.locator('.hfg_footer ul.footer-menu')
.first()
.waitFor();

const frame = page.frame({ name: 'customize-preview-0' });
if (!frame) {
throw new Error('Customizer preview frame not found.');
}

// Every placement has to advertise where it sits, otherwise selective refresh
// renders the partial once and copies that markup into all of them.
const contexts = await frame.evaluate(() =>
Array.from(
document.querySelectorAll('.builder-item--footer-menu')
).map((el) =>
el.getAttribute('data-customize-partial-placement-context')
)
);
expect(contexts).toEqual([
'{"device":"desktop","row":"bottom"}',
'{"device":"mobile","row":"top"}',
]);

const before = await footerMenuIds(frame);
expect(before).toEqual([
'footer-menu-desktop-bottom',
'footer-menu-mobile-top',
]);

// Changing a footer-menu setting refreshes the component partial.
await page.evaluate(() => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(window as any).wp
.customize('footer-menu_style')
.set('style-border-bottom');
});

// The style class only lands through a re-render, so this also proves the
// refresh actually ran before the ids are re-checked.
await expect(
preview
.locator(
'.footer--row[data-show-on="desktop"] .nav-menu-footer'
)
.first()
).toHaveClass(/style-border-bottom/);
await expect(
preview
.locator(
'.footer--row[data-show-on="mobile"] .nav-menu-footer'
)
.first()
).toHaveClass(/style-border-bottom/);

const after = await footerMenuIds(frame);
expect(after).toEqual(before);
await expect(preview.locator('ul#footer-menu')).toHaveCount(0);
});
}
);
Loading
Loading