Skip to content
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

Merged

Conversation

ryanjduffy
Copy link
Contributor

  • Closes

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 warning

image

I 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

Expected:

  • Tests are run

Actual:

  • Test are run but a warning is issued beforehand

How has the user experience changed?

PR Tasks

  • Have tests been added/updated?
  • Has the original issue (or this PR, if no issue exists) been tagged with a release in ZenHub? (user-facing changes only)
  • Has a PR for user-facing changes been opened in cypress-documentation?
  • Have API changes been updated in the type definitions?

@cypress-bot
Copy link
Contributor

cypress-bot bot commented Aug 18, 2022

Thanks for taking the time to open a PR!

@ryanjduffy ryanjduffy marked this pull request as ready for review August 29, 2022 22:35
Copy link
Contributor Author

@ryanjduffy ryanjduffy left a 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()
Copy link
Contributor Author

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!

Copy link
Contributor

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
Copy link
Contributor Author

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?

Copy link
Member

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.

Copy link
Contributor Author

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.

@nagash77 nagash77 requested a review from lmiller1990 September 7, 2022 18:16
@@ -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()
Copy link
Contributor

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])
Copy link
Contributor

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.

Copy link
Contributor Author

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?

Copy link
Contributor Author

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}`
    })
  }

Copy link
Contributor

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?

Copy link
Contributor Author

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.

@ryanjduffy ryanjduffy force-pushed the bug-detect-configured-browsers branch from 670fead to 074fcfd Compare September 8, 2022 20:23
@@ -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()
Copy link
Contributor Author

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) => {
Copy link
Member

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?

Copy link
Contributor Author

@ryanjduffy ryanjduffy Sep 9, 2022

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.

Copy link
Contributor Author

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!!

Copy link
Contributor

@lmiller1990 lmiller1990 left a 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)
Copy link
Contributor

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 () {
Copy link
Contributor

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?

Copy link
Contributor Author

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) => {
Copy link
Contributor

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?

Copy link
Contributor Author

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
Copy link
Contributor

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?

Copy link
Contributor

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.

Copy link
Contributor Author

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. ;)

Copy link
Contributor Author

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.

@ryanjduffy
Copy link
Contributor Author

@lmiller1990 - Pushed a commit to address you feedback. Thanks!

@lmiller1990 lmiller1990 self-requested a review September 20, 2022 07:36
Copy link
Contributor

@lmiller1990 lmiller1990 left a 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.

@lmiller1990
Copy link
Contributor

@emilyrohrbough WDYT? Any other comments?

@lmiller1990
Copy link
Contributor

lmiller1990 commented Sep 21, 2022

Oops, CI is failing - lemme take a look.

9ce44b4 should fix it.

@lmiller1990
Copy link
Contributor

Just need one more ✔️

@ryanjduffy
Copy link
Contributor Author

image

Copy link
Contributor

@ZachJW34 ZachJW34 left a 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

@lmiller1990
Copy link
Contributor

I will get it merged

@lmiller1990
Copy link
Contributor

Hmm I think the remaining failures are not unique to this branch. Let me take a look.

@lmiller1990
Copy link
Contributor

I pushed a commit make the timeout for the npm/schematic test longer.

@lmiller1990
Copy link
Contributor

lmiller1990 commented Sep 27, 2022

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 👍

@lmiller1990 lmiller1990 merged commit a75d3ec into cypress-io:develop Sep 27, 2022
@cypress-bot
Copy link
Contributor

cypress-bot bot commented Sep 27, 2022

Released in 10.9.0.

This comment thread has been locked. If you are still experiencing this issue after upgrading to
Cypress v10.9.0, please open a new issue.

@cypress-bot cypress-bot bot locked as resolved and limited conversation to collaborators Sep 27, 2022
@ryanjduffy ryanjduffy deleted the bug-detect-configured-browsers branch September 27, 2022 19:51
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants