From 6b0d3b6b2fd4e4cffc5475b37d4adcd550f281e0 Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Thu, 11 Jun 2026 12:29:59 +1000 Subject: [PATCH] fix(test): update e2e menu selectors (#1674) --- apps/e2e/fixtures/selectors.ts | 47 +++++++++--------------- apps/e2e/page-objects/player.ts | 14 ++----- apps/e2e/tests/captions.spec.ts | 9 +++-- apps/e2e/tests/visual/video-skin.spec.ts | 4 +- 4 files changed, 27 insertions(+), 47 deletions(-) diff --git a/apps/e2e/fixtures/selectors.ts b/apps/e2e/fixtures/selectors.ts index 27703029..7d7f4d4e 100644 --- a/apps/e2e/fixtures/selectors.ts +++ b/apps/e2e/fixtures/selectors.ts @@ -15,14 +15,22 @@ function withinControls(selector: string): string { return `media-controls ${selector}, .media-controls ${selector}`; } -/** Scope a descendant selector to each comma-separated panel matcher. */ -function withinMenuPanel(panelSelector: string, descendant: string): string { - return panelSelector +function unchecked(selector: string): string { + return selector .split(',') - .map((panel) => `${panel.trim()} ${descendant}`) + .map((part) => `${part.trim()}[aria-checked="false"]`) .join(', '); } +const menu = '[role="menu"]'; +const item = '[role="menuitem"]'; +const option = '[role="menuitemradio"]'; +const activeMenu = `${menu}[data-menu-view-state="active"]`; +const playbackRateOptions = [ + `#playback-rate-menu ${option}`, + withinControls(`.media-menu--playback-rate ${option}`), +].join(', '); + export const SELECTORS = { // Player containers // HTML: , React: wrapper div around VideoSkin @@ -47,37 +55,16 @@ export const SELECTORS = { withinControls('.media-button--playback-rate'), withinControls('button[aria-haspopup="menu"][aria-label^="Playback rate"]:not(.media-menu__item)'), ].join(', '), - playbackRateMenuPanel: '#playback-rate-menu[role="menu"], [role="menu"]#playback-rate-menu', - playbackRateMenuRadioItems: withinMenuPanel( - '#playback-rate-menu[role="menu"], [role="menu"]#playback-rate-menu', - '[role="menuitemradio"]' - ), - /** Standalone playback rate menu items (HTML id + React open menu in controls). */ - openPlaybackRateMenuRadioItems: [ - withinMenuPanel('#playback-rate-menu[role="menu"], [role="menu"]#playback-rate-menu', '[role="menuitemradio"]'), - withinControls('[role="menu"] [role="menuitemradio"]'), - ].join(', '), - /** Currently visible settings submenu panel (HTML + React). */ - activeMenuPanel: '[role="menu"][data-menu-view-state="active"]', - activeMenuRadioItems: '[role="menu"][data-menu-view-state="active"] [role="menuitemradio"]', + playbackRateUncheckedOptions: unchecked(playbackRateOptions), + activeMenuOptions: `${activeMenu} ${option}`, + activeMenuUncheckedOptions: unchecked(`${activeMenu} ${option}`), settingsButton: [ withinControls('.media-button--settings'), withinControls('button[commandfor="settings-menu"]'), withinControls('button[aria-label="Settings"]'), ].join(', '), - settingsCaptionsItem: [ - 'media-menu-item[commandfor="settings-captions-menu"]', - '[role="menuitem"]:has-text("Captions")', - ].join(', '), - settingsSpeedItem: ['media-menu-item[commandfor="settings-speed-menu"]', '[role="menuitem"]:has-text("Speed")'].join( - ', ' - ), - settingsSpeedMenuPanel: '#settings-speed-menu[role="menu"], [role="menu"]#settings-speed-menu', - settingsCaptionsMenuPanel: '#settings-captions-menu[role="menu"], [role="menu"]#settings-captions-menu', - settingsCaptionsMenuRadioItems: withinMenuPanel( - '#settings-captions-menu[role="menu"], [role="menu"]#settings-captions-menu', - '[role="menuitemradio"]' - ), + settingsCaptionsItem: `${item}:has-text("Captions")`, + settingsSpeedItem: `${item}:has-text("Speed")`, // Sliders // HTML: , React: horizontal .media-slider inside .media-time-controls diff --git a/apps/e2e/page-objects/player.ts b/apps/e2e/page-objects/player.ts index d48236c1..2170d041 100644 --- a/apps/e2e/page-objects/player.ts +++ b/apps/e2e/page-objects/player.ts @@ -166,7 +166,7 @@ export class PlayerPage { await this.settingsButton.click(); await expect(this.settingsSpeedItem).toBeVisible(); await this.settingsSpeedItem.click(); - await expect(this.page.locator(`${SELECTORS.activeMenuPanel} [role="menuitemradio"]`).first()).toBeVisible(); + await expect(this.page.locator(SELECTORS.activeMenuOptions).first()).toBeVisible(); } async openCaptionsSettings(): Promise { @@ -174,7 +174,7 @@ export class PlayerPage { await this.settingsButton.click(); await expect(this.settingsCaptionsItem).toBeVisible(); await this.settingsCaptionsItem.click(); - await expect(this.page.locator(SELECTORS.activeMenuRadioItems).first()).toBeVisible(); + await expect(this.page.locator(SELECTORS.activeMenuOptions).first()).toBeVisible(); } async getPlaybackRate(): Promise { @@ -236,16 +236,8 @@ export class PlayerPage { await this.playbackRateButton.click(); } - const uncheckedPlaybackRateOptions = SELECTORS.openPlaybackRateMenuRadioItems - .split(', ') - .map((selector) => `${selector.trim()}[aria-checked="false"]`) - .join(', '); const option = this.page - .locator( - usesSettingsMenu - ? `${SELECTORS.activeMenuPanel} [role="menuitemradio"][aria-checked="false"]` - : uncheckedPlaybackRateOptions - ) + .locator(usesSettingsMenu ? SELECTORS.activeMenuUncheckedOptions : SELECTORS.playbackRateUncheckedOptions) .first(); await expect(option).toBeVisible({ timeout: 5_000 }); // Menu popovers can intercept pointer events on nested radio items. diff --git a/apps/e2e/tests/captions.spec.ts b/apps/e2e/tests/captions.spec.ts index a50d8b98..f9d16f1e 100644 --- a/apps/e2e/tests/captions.spec.ts +++ b/apps/e2e/tests/captions.spec.ts @@ -1,5 +1,5 @@ import { expect, test } from '@playwright/test'; -import { SELECTORS } from '../fixtures/selectors'; +import { DATA_ATTRS, SELECTORS } from '../fixtures/selectors'; import { PlayerPage } from '../page-objects/player'; test.describe('Captions', () => { @@ -11,10 +11,11 @@ test.describe('Captions', () => { await player.waitForMediaReady(); }); - test('captions settings hidden without subtitle tracks', async () => { + test('captions settings are unavailable without subtitle tracks', async () => { await player.showControls(); await player.settingsButton.click(); - await expect(player.settingsCaptionsItem).toBeHidden(); + await expect(player.settingsCaptionsItem).toHaveAttribute(DATA_ATTRS.availability, 'unavailable'); + await expect(player.settingsCaptionsItem).toHaveAttribute('aria-disabled', 'true'); }); test('captions settings lists tracks when subtitle track is added', async ({ page }) => { @@ -33,7 +34,7 @@ test.describe('Captions', () => { await player.showControls(); await player.openCaptionsSettings(); - const options = page.locator(SELECTORS.activeMenuRadioItems); + const options = page.locator(SELECTORS.activeMenuOptions); await expect(options).toHaveCount(2, { timeout: 5_000 }); }); }); diff --git a/apps/e2e/tests/visual/video-skin.spec.ts b/apps/e2e/tests/visual/video-skin.spec.ts index 5c88f083..5ac53861 100644 --- a/apps/e2e/tests/visual/video-skin.spec.ts +++ b/apps/e2e/tests/visual/video-skin.spec.ts @@ -67,8 +67,8 @@ test.describe('Visual — Captions', () => { await player.showControls(); await player.openCaptionsSettings(); - await expect(page.locator(SELECTORS.activeMenuRadioItems)).toHaveCount(2); - await page.locator(SELECTORS.activeMenuRadioItems).nth(1).dispatchEvent('click'); + await expect(page.locator(SELECTORS.activeMenuOptions)).toHaveCount(2); + await page.locator(SELECTORS.activeMenuOptions).nth(1).dispatchEvent('click'); // Play briefly so the caption cue at 0:00 activates, then pause await player.play();