Skip to content

Commit d6d4c07

Browse files
authored
Fix macOS WebKit failures in Playwright keyboard tests (#15127)
macOS WebKit skips buttons and links on a plain Tab, because Safari's "Press Tab to highlight each item" setting is off by default. The tabset and off-screen revealjs slide tests now press tabKey(browserName), which sends Alt+Tab on WebKit. The axe report overlay ArrowDown test is skipped on macOS WebKit only, where it drops about half of ArrowDown presses on a focused scroll region, even on a plain HTML page with no Quarto code. PageDown still covers the overlay there. - Tabset keyboard test and off-screen slide focus tests use tabKey - Focus-stays-in-deck tests use tabKey - ArrowDown variant of the axe overlay scroll test is skipped on macOS WebKit See #15007, #15010, #15089, #15094, #14713, #14816
1 parent 83f015f commit d6d4c07

3 files changed

Lines changed: 30 additions & 22 deletions

File tree

‎tests/integration/playwright/tests/axe-accessibility.spec.ts‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -839,7 +839,14 @@ test.describe('HTML axe — the report overlay scrolls by keyboard (#14680)', ()
839839
// overlay self-scan (scrollable-region-focusable, line ~388), so only an
840840
// actual key press that moves scrollTop catches them.
841841
for (const key of ['PageDown', 'ArrowDown']) {
842-
test(`html — focused overlay scrolls on ${key}`, async ({ page }) => {
842+
test(`html — focused overlay scrolls on ${key}`, async ({ page, browserName }) => {
843+
// WebKit on macOS ignores about half of ArrowDown presses on a focused
844+
// scroll region, even a plain HTML one with no Quarto code; PageDown
845+
// scrolls reliably and still covers the overlay.
846+
test.skip(
847+
key === 'ArrowDown' && browserName === 'webkit' && process.platform === 'darwin',
848+
'WebKit on macOS drops ArrowDown on a focused scroll region',
849+
);
843850
await page.goto('/html/axe-overlay-scroll.html', { waitUntil: 'networkidle' });
844851

845852
const axeReport = page.locator('.quarto-axe-report');

‎tests/integration/playwright/tests/html-tabsets.spec.ts‎

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { test, expect } from '@playwright/test';
2+
import { tabKey } from '../src/utils';
23

34
test('Jupyter - Creates working tabsets from for loops', async ({ page }) => {
45
await page.goto('/html/tabsets/jupyter-tabsets.html');
@@ -25,19 +26,19 @@ test.describe('Tabby tabsets (theme: none) keyboard navigation', () => {
2526
await expect(page.getByRole('tablist')).toBeAttached();
2627
});
2728

28-
test('Tab moves from the selected tab into its panel (#730)', async ({ page }) => {
29+
test('Tab moves from the selected tab into its panel (#730)', async ({ page, browserName }) => {
2930
await page.getByRole('button', { name: 'Button before the tabset' }).focus();
30-
await page.keyboard.press('Tab');
31+
await page.keyboard.press(tabKey(browserName));
3132
await expect(page.getByRole('tab', { name: 'Tab A' })).toBeFocused();
32-
await page.keyboard.press('Tab');
33+
await page.keyboard.press(tabKey(browserName));
3334
await expect(page.getByRole('button', { name: 'Button in tab A' })).toBeFocused();
3435
await expect(page.getByRole('tab', { name: 'Tab A' })).toHaveAttribute('aria-selected', 'true');
3536
});
3637

37-
test('Shift+Tab from the panel returns to the selected tab', async ({ page }) => {
38+
test('Shift+Tab from the panel returns to the selected tab', async ({ page, browserName }) => {
3839
await page.getByRole('tab', { name: 'Tab B' }).click();
3940
await page.getByRole('button', { name: 'Button in tab B' }).focus();
40-
await page.keyboard.press('Shift+Tab');
41+
await page.keyboard.press(tabKey(browserName, true));
4142
await expect(page.getByRole('tab', { name: 'Tab B' })).toBeFocused();
4243
});
4344

‎tests/integration/playwright/tests/revealjs.spec.ts‎

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -113,27 +113,27 @@ test.describe('off-screen slides are not focusable (#14795)', () => {
113113
const currentSlideId = (page: Page) =>
114114
page.evaluate(() => (window as any).Reveal.getCurrentSlide().id);
115115

116-
test('Tab on a slide reaches only that slide (#14795)', async ({ page }) => {
116+
test('Tab on a slide reaches only that slide (#14795)', async ({ page, browserName }) => {
117117
await gotoSlide(page, '#/slide-3');
118-
await page.keyboard.press('Tab');
118+
await page.keyboard.press(tabKey(browserName));
119119
await expect(slideButton(page, 'slide-3')).toBeFocused();
120-
await page.keyboard.press('Tab');
120+
await page.keyboard.press(tabKey(browserName));
121121
await expect(page.locator('.reveal .slides section :focus')).toHaveCount(0);
122122
});
123123

124-
test('Tab after keyboard navigation stays on the new slide', async ({ page }) => {
124+
test('Tab after keyboard navigation stays on the new slide', async ({ page, browserName }) => {
125125
await gotoSlide(page, '#/slide-2');
126126
await page.keyboard.press('ArrowRight');
127127
await expect(page).toHaveURL(/#\/slide-3$/);
128-
await page.keyboard.press('Tab');
128+
await page.keyboard.press(tabKey(browserName));
129129
await expect(slideButton(page, 'slide-3')).toBeFocused();
130130
});
131131

132-
test('Tab in a vertical stack reaches only the current child', async ({ page }) => {
132+
test('Tab in a vertical stack reaches only the current child', async ({ page, browserName }) => {
133133
await gotoSlide(page, '#/stack-b');
134-
await page.keyboard.press('Tab');
134+
await page.keyboard.press(tabKey(browserName));
135135
await expect(slideButton(page, 'stack-b')).toBeFocused();
136-
await page.keyboard.press('Tab');
136+
await page.keyboard.press(tabKey(browserName));
137137
await expect(focusOutsideCurrentSlide(page)).toHaveCount(0);
138138
});
139139

@@ -143,15 +143,15 @@ test.describe('off-screen slides are not focusable (#14795)', () => {
143143
await expect(slideButton(page, 'slide-6')).toBeFocused();
144144
});
145145

146-
test('overview: slides are clickable, inert returns on exit', async ({ page }) => {
146+
test('overview: slides are clickable, inert returns on exit', async ({ page, browserName }) => {
147147
await gotoSlide(page, '#/slide-3');
148148
await page.keyboard.press('o');
149149
await expect(page.locator('.reveal.overview')).toBeAttached();
150150
// Corner click: a centre click focuses the button on mousedown, so the next Tab would skip it.
151151
await page.locator('section#slide-5').click({ position: { x: 5, y: 5 } });
152152
await expect(page).toHaveURL(/#\/slide-5$/);
153153
await expect(page.locator('.reveal.overview')).toHaveCount(0);
154-
await page.keyboard.press('Tab');
154+
await page.keyboard.press(tabKey(browserName));
155155
await expect(slideButton(page, 'slide-5')).toBeFocused();
156156
});
157157

@@ -162,7 +162,7 @@ test.describe('off-screen slides are not focusable (#14795)', () => {
162162
await expect(slideButton(page, 'slide-4')).toBeFocused();
163163
});
164164

165-
test('scroll view toggle with R: focusable inside, current slide reachable after leaving', async ({ page }) => {
165+
test('scroll view toggle with R: focusable inside, current slide reachable after leaving', async ({ page, browserName }) => {
166166
await gotoSlide(page, '#/slide-2');
167167
await page.keyboard.press('R');
168168
await expect.poll(() => isScrollView(page)).toBe(true);
@@ -173,11 +173,11 @@ test.describe('off-screen slides are not focusable (#14795)', () => {
173173
await expect.poll(() => currentSlideId(page)).toBe('slide-4');
174174
await page.keyboard.press('R');
175175
await expect.poll(() => isScrollView(page)).toBe(false);
176-
await page.keyboard.press('Tab');
176+
await page.keyboard.press(tabKey(browserName));
177177
await expect(slideButton(page, 'slide-4')).toBeFocused();
178178
});
179179

180-
test('responsive scroll view below scrollActivationWidth', async ({ page }) => {
180+
test('responsive scroll view below scrollActivationWidth', async ({ page, browserName }) => {
181181
await gotoSlide(page, '#/slide-2');
182182
await page.setViewportSize({ width: 400, height: 700 });
183183
await expect.poll(() => isScrollView(page)).toBe(true);
@@ -187,7 +187,7 @@ test.describe('off-screen slides are not focusable (#14795)', () => {
187187
await expect.poll(() => currentSlideId(page)).toBe('slide-4');
188188
await page.setViewportSize({ width: 1280, height: 720 });
189189
await expect.poll(() => isScrollView(page)).toBe(false);
190-
await page.keyboard.press('Tab');
190+
await page.keyboard.press(tabKey(browserName));
191191
await expect(slideButton(page, 'slide-4')).toBeFocused();
192192
});
193193

@@ -209,14 +209,14 @@ test.describe('the focus stays in the deck when its slide is left (#15078)', ()
209209
await expect(page.locator('.reveal.ready')).toBeAttached();
210210
});
211211

212-
test('moving on from the slide that has the focus hands it to the deck', async ({ page }) => {
212+
test('moving on from the slide that has the focus hands it to the deck', async ({ page, browserName }) => {
213213
await slideButton(page, 'slide-2').focus();
214214
await page.keyboard.press('ArrowRight');
215215
await expect(page).toHaveURL(/#\/slide-3$/);
216216
await expect(deck(page)).toBeFocused();
217217
await expect(deck(page)).toHaveAttribute('tabindex', '-1');
218218
// From the deck, Tab goes on into the new slide.
219-
await page.keyboard.press('Tab');
219+
await page.keyboard.press(tabKey(browserName));
220220
await expect(slideButton(page, 'slide-3')).toBeFocused();
221221
});
222222

0 commit comments

Comments
 (0)