Say what stood in the way when a click never lands
The two full-screen range picker tests give up after the full timeout on the runner's Firefox, and nowhere else. Playwright waits for a click target to be visible, still, enabled and on top, and reports none of those when it gives up — so the failure says only that 45 seconds passed. Both tests now name the element the browser would hit at the toggle's own centre before they press it (`hitTarget()`), so a covered or undrawn control fails with what covered it rather than with a timeout. The run also keeps the screenshots Playwright writes for a failure, as an artifact: a page that only misbehaves there is otherwise invisible from a workstation where all three engines pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
1b38b39a47
commit
dd76e79877
@@ -102,3 +102,15 @@ jobs:
|
||||
run: vendor/bin/pest --testsuite=Browser --browser ${{ matrix.browser }}
|
||||
env:
|
||||
BROWSER_TIMEOUT: 45000
|
||||
|
||||
# What the runner saw when an assertion or an action gave up. A failure that does not
|
||||
# reproduce on a workstation is otherwise only a stack trace: the page itself says whether an
|
||||
# element was covered, unstyled or never drawn.
|
||||
- name: Keep the screenshots of what failed
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: screenshots-${{ matrix.browser }}
|
||||
path: tests/Browser/Screenshots
|
||||
if-no-files-found: ignore
|
||||
retention-days: 7
|
||||
|
||||
@@ -503,6 +503,34 @@ it('keeps each picker\'s own settings inside a page\'s outer x-data scope', func
|
||||
->assertScript('! (\'min\' in Alpine.$data(document.querySelector(\'[x-data="{ step: 1 }"]\')))');
|
||||
});
|
||||
|
||||
/**
|
||||
* What the browser would hit at the centre of `$selector`, as `tag#id[attributes]`, and whether the
|
||||
* element itself is the answer. Playwright waits for a click target to be visible, still and on top,
|
||||
* and gives up with nothing but a timeout when it never is; this says what stood in the way, on a
|
||||
* runner where a layout that works elsewhere does not.
|
||||
*/
|
||||
function hitTarget(string $selector): string
|
||||
{
|
||||
return <<<JS
|
||||
(() => {
|
||||
const target = document.querySelector("{$selector}")
|
||||
|
||||
if (! target) return 'nothing matches {$selector}'
|
||||
|
||||
const box = target.getBoundingClientRect()
|
||||
const top = document.elementFromPoint(box.left + box.width / 2, box.top + box.height / 2)
|
||||
|
||||
if (top === null) return `outside the viewport: \${JSON.stringify(box)}`
|
||||
if (target === top || target.contains(top)) return 'the target'
|
||||
|
||||
const name = (element) => element.tagName.toLowerCase() + (element.id ? '#' + element.id : '')
|
||||
+ [...element.attributes].filter((a) => a.name.startsWith('data-md')).map((a) => `[\${a.name}]`).join('')
|
||||
|
||||
return `\${name(top)} covers \${name(target)}`
|
||||
})()
|
||||
JS;
|
||||
}
|
||||
|
||||
it('opens the range picker full screen below 600px and grows its month list both ways by keyboard paging', function () {
|
||||
$picker = "document.querySelector('#trip-field-picker')";
|
||||
$scope = "Alpine.\$data(document.querySelector('[data-md-datepicker]:has(#trip-field)'))";
|
||||
@@ -512,8 +540,11 @@ it('opens the range picker full screen below 600px and grows its month list both
|
||||
// Explicit, not implicit: the resize's own reflow can still be under way when the click
|
||||
// that follows fires, which is what made this time out on a loaded runner — the toggle's
|
||||
// box kept moving under the click rather than the click itself being slow.
|
||||
->assertScript('window.innerWidth === 390 && document.documentElement.getAnimations({ subtree: true }).length === 0')
|
||||
->click('[aria-controls="trip-field-picker"][data-md-datepicker-toggle]')
|
||||
->assertScript('window.innerWidth === 390 && document.documentElement.getAnimations({ subtree: true }).length === 0');
|
||||
|
||||
expect($page->script(hitTarget('[aria-controls=\"trip-field-picker\"][data-md-datepicker-toggle]')))->toBe('the target');
|
||||
|
||||
$page->click('[aria-controls="trip-field-picker"][data-md-datepicker-toggle]')
|
||||
->assertScript("{$picker}.dataset.mdPresentation === 'full'")
|
||||
->assertScript("{$picker}.matches(':modal')")
|
||||
->assertScript("document.querySelector('#trip-field-picker [data-md-datepicker-app-bar]').offsetHeight > 0")
|
||||
@@ -557,10 +588,13 @@ it('opens the range picker full screen below 600px and grows its month list both
|
||||
it('closes the full-screen range picker from its app bar close button without keeping the draft', function () {
|
||||
$picker = "document.querySelector('#trip-field-picker')";
|
||||
|
||||
dateProbe()
|
||||
$page = dateProbe()
|
||||
->resize(390, 800)
|
||||
->assertScript('window.innerWidth === 390 && document.documentElement.getAnimations({ subtree: true }).length === 0')
|
||||
->click('[aria-controls="trip-field-picker"][data-md-datepicker-toggle]')
|
||||
->assertScript('window.innerWidth === 390 && document.documentElement.getAnimations({ subtree: true }).length === 0');
|
||||
|
||||
expect($page->script(hitTarget('[aria-controls=\"trip-field-picker\"][data-md-datepicker-toggle]')))->toBe('the target');
|
||||
|
||||
$page->click('[aria-controls="trip-field-picker"][data-md-datepicker-toggle]')
|
||||
->click(day('trip-field', '2026-09-20'))
|
||||
->click('#trip-field-picker [data-md-datepicker-close]')
|
||||
->assertScript("! {$picker}.open")
|
||||
|
||||
Reference in New Issue
Block a user