-
Notifications
You must be signed in to change notification settings - Fork 3.3k
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
fix: Detect user-configured browsers #23446
fix: Detect user-configured browsers #23446
Conversation
Thanks for taking the time to open a PR!
|
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.
I think this is ready-ish. Left a couple discussion points below. Could also use some advice on adding tests for this.
@@ -46,9 +47,20 @@ export function makeDataContext (options: MakeDataContextOptions): DataContext { | |||
close: browsers.close, | |||
getBrowsers, | |||
async ensureAndGetByNameOrPath (nameOrPath: string) { | |||
const projectConfig: FullConfig = await (ctx as DataContext).project.getConfig() |
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.
This type assertion seems like it should be unnecessary (and isn't used below) but I couldn't avoid the any
without it. Improvements welcome!
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.
Hi 👋
You can probably do const projectConfig = await getCtx().project.getConfig()
to get the correct type inference (which will return a reference to ctx
).
@@ -46,9 +47,20 @@ export function makeDataContext (options: MakeDataContextOptions): DataContext { | |||
close: browsers.close, | |||
getBrowsers, | |||
async ensureAndGetByNameOrPath (nameOrPath: string) { | |||
const projectConfig: FullConfig = await (ctx as DataContext).project.getConfig() | |||
const declaredBrowsers = projectConfig.browsers?.map<FoundBrowser | undefined>((b) => { | |||
if (!b.path) return |
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.
It seems that only browsers injected by the user or plugins have a path
set so I'm using it as the proxy for custom browsers. Seem reasonable?
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.
@ryanjduffy Thank you for opening a change to update improve this check! Considering where this is used, it;'d likely be cheaper and cleaner from a start-up perspective to update ensureAndGetByNameOrPath
to be a pass through to the imported ensureAndGetByNameOrPath
getBrowsers,
- async ensureAndGetByNameOrPath (nameOrPath: string) {
- ...
- },
+ ensureAndGetByNameOrPath
and instead update where this is called in the data-context package to supply the list of browsers.
Jumping through the flow:
- we set the active browser after we finish loading the config
- this will then call
ensureAndGetByNameOrPath
if a browser was provided via the CLI. Seems like we should be able to pull up the machine browsers check and pass it in here.
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.
@emilyrohrbough Appreciate the feedback! It's helped me track how this data is used. I've taken a slightly different approach by consolidating both browser lookups (machine and user) into BrowserDataSource
and then exposed an allBrowsers
method to fetch both. I like that this approach keeps the "concerns" of the browser config in one spot and makes the diff more clear.
The added bonus of this was that it allowed me to fix cypress open
to remember the last browser when it was a user browser since that logic can now rely on allBrowsers
.
@@ -46,9 +47,20 @@ export function makeDataContext (options: MakeDataContextOptions): DataContext { | |||
close: browsers.close, | |||
getBrowsers, | |||
async ensureAndGetByNameOrPath (nameOrPath: string) { | |||
const projectConfig: FullConfig = await (ctx as DataContext).project.getConfig() |
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.
Hi 👋
You can probably do const projectConfig = await getCtx().project.getConfig()
to get the correct type inference (which will return a reference to ctx
).
const browsers = await ctx.browser.machineBrowsers() | ||
|
||
return await ensureAndGetByNameOrPath(nameOrPath, false, browsers) | ||
return await ensureAndGetByNameOrPath(nameOrPath, false, [...declaredBrowsers, ...browsers]) |
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.
I wonder if we want to do a check to make sure there isn't any duplicates here - it shouldn't really happen, but if someone adds a default browser via cypress.config.ts
for whatever reason, it'd show up twice.
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.
What would you de-dupe on? family / major version / minor version / path?
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.
Just ran across a removeDuplicates
method elsewhere which uses name and version
const removeDuplicates = (val) => {
return _.uniqBy(val, (browser: FoundBrowser) => {
return `${browser.name}-${browser.version}`
})
}
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.
If this is the pattern, maybe we just go with that. I wonder if we can make that a reused utility? Thoughts?
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.
I've consolidated this into BrowserDataSource
and exported it from there.
670fead
to
074fcfd
Compare
@@ -296,7 +296,7 @@ export class ProjectLifecycleManager { | |||
|
|||
// lastBrowser is cached per-project. | |||
const prefs = await this.ctx.project.getProjectPreferences(path.basename(this.projectRoot)) | |||
const browsers = await this.ctx.browser.machineBrowsers() | |||
const browsers = await this.ctx.browser.allBrowsers() |
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.
This also fixes restoring the last browser if it's added via the config
const p = this.ctx.project.getConfig() | ||
|
||
this.ctx.coreData.userBrowsers = p.then(async (cfg) => { | ||
return cfg.browsers?.map<FoundBrowser | undefined>((b) => { |
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.
at this point, won't the cfg.browsers
include the found machine browsers as well as the user-defined browsers?
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.
It does! I didn't catch that. What is the authoritative source for "browsers from the config file" (is this a thing?) vs "browsers added via a plugin" vs "browsers on the machine" vs "resolved browsers from the full config" ?
I'll push a commit that tries to get "1" and "2" via "4" - "3" but it seems suspect.
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.
@emilyrohrbough - following up to see if you have feedback on the updates I pushed. Thanks!!
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.
Looks good, just some nits. Also we don't appear to have any coverage around this - I'm still thinking about how best to add that. It might be quicker if someone internally picks that up, just so we can get this over the line.
Curious about the catch
you've added - can you think of a specific example we might need to test for that where the logic you've added might error?
} | ||
|
||
export function removeDuplicateBrowsers (browsers: FoundBrowser[]) { | ||
return _.uniqBy(browsers, getBrowserKey) |
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.
Oh neat, I forgot about uniqBy
!
/** | ||
* Gets the browsers from the machine and the project config | ||
*/ | ||
allBrowsers () { |
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.
I think we can move this to use the async/await
style - I know you are just matching the code style, but in general I think we are moving away from big nested then
chains in favor of the more modern async/await. Thoughts?
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.
Fine with me. As you guessed, I was just matching style
this.ctx.coreData.allBrowsers = Promise.all([p, machineBrowsers]).then(async ([cfg, m]) => { | ||
if (!cfg.browsers) return m | ||
|
||
const userBrowsers = cfg.browsers.map<FoundBrowser | undefined>((b) => { |
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.
Whenever I see a map
then filter
combo I like to opt for reduce
to decrease the number of iterations. Unless you think that would be more difficult to read?
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.
Not a strong opinion. Some folks find reduce to be less readable. If there's a preference for reduce, I can make that change.
}).catch((e) => { | ||
this.ctx.update((coreData) => { | ||
coreData.allBrowsers = null | ||
coreData.diagnostics.error = e |
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.
Nice job figuring out where/how to propagate this error, the data-context is pretty confusing. Did you actually verify this error manifests in the UI/CLI?
I'm also not entirely clear on the condition that would lead to this - is there one, or did you just put it here for good measure?
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.
Gonna need a quick test for both cases, I don't think we have much coverage around detecting the browsers in general, I could pick this up.
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.
I don't think the new logic will likely error. machineBrowsers()
re-throws so that'll be caught here but really you're just getting two copies of the same error then. Lemme experiment a bit to see if I can break 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.
Looks like the config validation logic would catch the cases in which this code would fail so I can remove the error handling.
@lmiller1990 - Pushed a commit to address you feedback. Thanks! |
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.
Looks good, I wrote some tests 41c296d then made a small refactor (code style - I really prefer return early than nesting lots of logic in an if
statement, fairly confident everything still works since I wrote the tests before the refactor 👍 )
Not related to this PR but I think we should not be mutating coreData in *DataSource
, but in *Actions
(so in BrowserActions.ts
). I think changing this is a big outside the scope of this, so going to approve this for now - code does what it's supposed to.
@emilyrohrbough WDYT? Any other comments? |
Oops, CI is failing - lemme take a look. 9ce44b4 should fix it. |
Just need one more ✔️ |
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.
Works great! Let's get CI to run and then get this merged
I will get it merged |
Hmm I think the remaining failures are not unique to this branch. Let me take a look. |
I pushed a commit make the timeout for the npm/schematic test longer. |
This is passing on CI https://app.circleci.com/pipelines/github/cypress-io/cypress/44027/workflows/3c5e7436-590d-4360-afcb-114d18b56c53 I don't know why the status did not update. I'm going to perform an admin merge, since I've visually inspected this is all passing on CI, and I've also run the tests locally that are related to this code change. Merged... took a while, but we did it 👍 |
Released in This comment thread has been locked. If you are still experiencing this issue after upgrading to |
User facing changelog
Adding a custom browser configuration will no longer emit a warning when trying to use that browser
Additional details
If you specify a custom browser (either directly in
setupNodeEvents
or via a plugin) and try to use that browser via--browser
, Cypress logs a warningI expect this isn't the correct fix for this problem hence the draft status but hoping to get some feedback. The issue seems to be that the logic within
makeDataContext
only resolves the machine browsers based on the known browsers and ignores any user-configured browsers. Perhaps I'm just configuring things wrong and there's another way to do it.Steps to test
npx cypress run --browser Brave
(or whatever you've called the custom browser*Expected:
Actual:
How has the user experience changed?
PR Tasks
cypress-documentation
?type definitions
?