-
Notifications
You must be signed in to change notification settings - Fork 336
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Spruce up Puppeteer usage to stop timeouts, page conflicts etc #2897
Conversation
8d3d5e1
to
0bd8e22
Compare
@@ -47,7 +49,7 @@ describe('/components/button', () => { | |||
]) | |||
|
|||
const url = await page.url() | |||
expect(url).toBe(baseUrl + href) | |||
expect(url).toBe(`http://localhost:${PORT}${href}`) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To avoid needing to know the port in this file, wondering if we could match on just the paths?
Think we could use HTMLElement.pathname
when evaluating href
(rather than reading the href
attribute).
Less sure on the best way to get the current page's path out of Puppeteer!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perfect, I didn't feel happy losing the port but I've got your support now 😊
expect(url).toBe(`http://localhost:${PORT}${href}`) | |
const { pathname } = new URL(url) | |
expect(pathname).toBe(href) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done 👍
0bd8e22
to
aece8dd
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's neat, really appreciate the goTo
and goTo...
helpers that avoid dragging a baseUrl
in each tests 🙌🏻 Just go a little nitpick on a destructuring assignment and a question on the keyboard throttling.
const { getExamples } = require('../../../../lib/jest-helpers.js') | ||
const { goToComponent, renderAndInitialise } = require('../../../../lib/puppeteer-helpers') | ||
|
||
// Detect when browser has been launched headless | ||
const { launch: { headless = true } } = jestPuppeteerConfig |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Two levels of destructuring muddies that it's headless
that ends up as the const
. I think it's clearer as const headless = jestPuppeteerConfig.launch.headless
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Absolutely, I'll change it 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@romaricpascal Hope you don't mind a single (with default) destructured property?
Otherwise it defaults to undefined
as it's not in the launch config
// Detect when browser has been launched headless
const { headless = true } = jestPuppeteerConfig.launch
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How do we feel about the Nullish coalescing operator?
// Detect when browser has been launched headless
const headless = jestPuppeteerConfig.launch.headless ?? true;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, it was having the two levels of destructuring that I think is too much (not sure if we can lint for that, but that's a separate issue).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How do we feel about the Nullish coalescing operator?
// Detect when browser has been launched headless const headless = jestPuppeteerConfig.launch.headless ?? true;
Yeah I love it BUT only where falsy things 0
, ''
, undefined
are actually wanted 😮
const headless = jestPuppeteerConfig.launch.headless || true; // true
const headless = jestPuppeteerConfig.launch.headless ?? true; // 0
That said, it's super useful in assignment form with user defaults you don't want to override by accident:
process.env.NODE_ENV ??= 'development'
For me, I think I'm so used to ESM import from
+ import()
that simple destructuring feels neater?
import { example } from './examples.mjs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's not my understanding of how ??
works…
node
Welcome to Node.js v16.17.0.
Type ".help" for more information.
> const config = { truthy: true, falsey: false, nullish: null }
undefined
> config.truthy ?? true
true
> config.falsey ?? true
false
> config.nullish ?? true
true
> config.undefined ?? true
true
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry I used the assignment form ??=
above
Depends if we want to guard against config values?
launch: { headless: 0 }
launch: { headless: '' }
launch: { headless: NaN }
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Personally, I'm not too concerned about launch.headless
being something unexpected as it's in our control, and find the coalesced version easier to understand than the destructured version. But recognise this is probably down to a lack of familiarity with how de-structuring with defaults works and basically personal preference, so this isn't blocking.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @36degrees, appreciate the feedback
I suppose it's deciding when something's ready to be added to the code style arsenal?
const hello = example.hello // Yes
const hello = example['hello'] // Yes
const { hello } = example // Maybe?
With all the clever Sass we do, it's nice to have the same artistic licence in JavaScript too 🎩
// Start with three
const more = {
one: 1,
two: 2,
three: 3
}
// Take one out
const { one, ...less } = more
// Then there were two
console.log(less)
// { two: 2, three: 3 }
(That would be a bit too clever)
// in background pages will be aligned, clamped or throttled | ||
const debouncedWaitTime = headless ? 1500 : 2000 | ||
|
||
// To avoid typing lag, keyup events are also throttled automatically |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The different wait time is pretty clear, but I'm not understanding this explanation about throttling the keyup events. Why do they need to be throttled when the browser is not headless?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remember back in the day, using a slower device (old Android phone) and you're typing furiously into a <textarea>
with key event listeners, yet no characters are appearing? There was a bit of a lag
With { headless: false }
I'm either seeing Chromium automatically debounce keyup
events under heavy load (lots of concurrent tests running), background event throttling, or an issue with the Character Count component.
Try it and see? 😭
Open jest-puppeteer.config.js and add launch: { headless: false }
Tests that type in letters too fast will see the screen reader status not change
Might need a better description if it's not clear?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Argh, damn. Yeah, I think a better description describing that Chrome messes with keyup
events under load would help future us 😄
I'm also wondering if we shouldn't move the component out of using keyboard events for detecting inputs and use the input
Event. I believe this'll have to wait the work on browser support though as that event is not supported in IE8 😢
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@romaricpascal @36degrees How's this new explanation?
// When headless, keydown-to-keyup time appears to affect event throttling,
// affecting `lastInputTimestamp` and screen reader status message updates
const keyupWaitTime = headless ? 0 : 50
I'm not 100% on the explanation as perhaps it's related to the whole string of key presses taking less than 500ms, affecting the this.lastInputTimestamp
Date.now() check etc?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@romaricpascal Yeah input
is very interesting. It's probably the fact that the older Input Events (including keyup
) are technically "cancellable" so for performance maybe they do debounce?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for this, @colinrotherham 😊 👏🏻
I'm happy that the things I flagged have been addressed, but agree with Romaric's comments so would like to see them resolved before merging.
Suggestion during code review that `page.waitForSelector()` can be used instead
…t runs Although parallel browser test runs will still push windows from foreground to background, this change ensures that initial `.focus()` events will still fire
aece8dd
to
24532e7
Compare
…eadless Timers in background pages will be aligned, clamped or throttled https://developer.chrome.com/blog/background_tabs whatwg/html#7941
Allows more time for slower machines to complete the entire test suite without individual tests timing out
24532e7
to
3d885e7
Compare
This PR has now been split into two
Part 1 – #2897 Spruce up Puppeteer usage to stop timeouts, page conflicts etc (this one)
Part 2 – #2861 Use async file operations for helpers
Everything "Puppeteer" runs reliably locally now, with each change is split up by commit so it's clearer what's changed
What's included
page
{ headless: false }
Some examples:
.focus()
tests won't pass when tabs/windows are opened in the backgroundpage.waitForSelector()
could have different pages than viapage.goto()
page.setJavaScriptEnabled(false)
More info on Google Chrome background tab timer changes
WHATWG discussion notes: Throttling and clamping of setTimeout and setInterval
What's not included
When running "headed"
{ headless: false }
it's still impossible for multiple windows to have adocument.activeElement
in focus. Thankfully as headless mode is the default this can be a fix for another dayPerhaps grabbing the page earlier than
{ waitUntil: 'load' }
and before.initAll()
, then waiting for thefocus
event to fire would be more reliable than expecting focus to still be set "right now".