-
Notifications
You must be signed in to change notification settings - Fork 3.2k
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
feat: run filtered debug tests in runner #25265
feat: run filtered debug tests in runner #25265
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.
Just a preliminary review. Most of my questions are around the mashing of the test filter fields into the Spec type. I am guessing you did that in order to get them to ride along with the Spec when it is passed into the runner. Would it be hard to pass the test filter through separately?
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 wasn't as complex as expected, nice job figuring this out. Just some early bird comments.
} | ||
|
||
const Header = observer(({ appState, events = defaultEvents, statsStore }: ReporterHeaderProps) => ( | ||
const Header = observer(({ appState, events = defaultEvents, statsStore, spec }: ReporterHeaderProps) => ( |
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 needing to do this now, but we really need to solve App <-> Command Log communication. Although this works for now, I see this becoming a pretty big mess. I don't have any great ideas, but some kind of unified way to bridge the Vue <-> React gap would be great. Let's keep this is mind, definitely open to solutions to this.
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 looked at the M2 work and there is a lot more communication coming up. We do get reactivity from the mobxrunnerstore
, maybe we can setup a system whereby any change made to a pinia store is relayed to the mobx store and wrapped with runInAction
@warrensplayer @lmiller1990 haven't addressed all the comments but did a pretty large refactor. Pulled the testFilter off the |
@emilyrohrbough added you as a reviewer as I'm not too experienced with driver code and could use another pair of eyes outside of the CT team! |
packages/driver/src/cypress.ts
Outdated
@@ -194,6 +195,7 @@ class $Cypress { | |||
this.browser = config.browser | |||
this.platform = config.platform | |||
this.testingType = config.testingType | |||
this.testFilter = config.testFilter |
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.
Setting this on the Cypress instance like this prob makes sense to run this faster, but a new run finishes on the cloud with updated results, how will this reflect here? I assume the user would need to navigate away from this spec and come back to get the updated test filter?
Also am I understanding the reason for adding this -- to filter down flakey (or is it failed) cloud test results? If that's the case can we name this accordingly? testFilter sounds like a user-provided filter value and doesn't indicate to me this value is driven from cloud data. Would be helpful for maintenance and change impact if this ever needed updated.
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 idea was to keep the filtering somewhat generic, even though it has only one use case to start. I would think that the driver would not care why the tests were being filtered, just that it was asked to filter them. Our thoughts is that this could be expanded in the future to add more ways to filter tests.
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 can see that...Though it would typically think filter being a regex or list of regex patterns. Which maybe that could be the case later one. The fact it's stored on the global Cypress instance vs state make it feel like this value will be "static" for this Cypress instance.
This request #4886, would likely use some of this logic if we chose to support them, which would be driven by user actions in the reporter and not necessarily create a new Cypress instance. I also feel like there was another request recently for selecting a single test in the command long to run in .only()
mode for quick debugging - just couldn't find the 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.
I think generating a new Cypress instance would be the cleanest solution as we already have all the wiring for restarting a spec (spec file changed, hitting the refresh button). Might be less performant but restarting has never been too expensive to my knowledge. When we restart, we get another pass at the mocha suite which allows the testFilter
to get applied again. This testFilter could be modified with each cycle, enabling cypress-io/cypress/issues/4886 by listening to an event capturing the run results and changing the filter based on what failed, then triggering a restart.
I could change the filter to be regex/callback rather than string array which would add more utility to it.
} | ||
|
||
const normalizeAll = (suite, initialTests = {}, setTestsById, setTests, getRunnableId, getHookId, getOnlyTestId, getOnlySuiteId, createEmptyOnlyTest, incrementFoundTestsBy) => { | ||
pruneEmptySuites(suite, incrementFoundTestsBy) |
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 confirm, we will only prune in open mode correct? if so, can we add a comment to clarify and/or can we skip this all together?
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.
Can we add some unit tests around this behavior? https://github.com/cypress-io/cypress/blob/3925ae062a9b9dca4cc9d61391ce5f6ff0525a8b/packages/driver/cypress/e2e/cypress/runner.cy.js
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 sure there is a good place to test this outside of the cy-in-cy
test since the testFilter needs to be set before the spec file is executed.
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.
We should be able to mock the testFilter
value in unit tests and ensure the correct runnable results are returned.
} | ||
|
||
const pruneEmptySuites = (rootSuite, incrementFoundTestsBy) => { | ||
for (const suite of [...rootSuite.suites]) { |
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.
for (const suite of [...rootSuite.suites]) { | |
if (!Cypress.testFilter) { | |
return // do nothing if the test filter is not set. | |
} | |
for (const suite of [...rootSuite.suites]) { |
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.
We still want to prune empty suites regardless of a testFilter
being present or not so that the UI doesn't show the empty suite dropdowns.
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.
Revisiting this, can I get your opinion on what you think the desired behavior is regarding empty suites e.g. describe('empty', () => {})
?
Currently they show up in the UI as an empty runnable. Looks a bit wonky but has no side effects. With the way things are wired up now, these suites are pruned (regardless of if there is a test filter or not). What could be confusing is if a developer goes to test some new functionality and starts with describe.only('new test coverage', () => {})
and they hit save. Since the suite has no children, it will get pruned and the only
will not apply. This behavior seems pretty confusing to me
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 moved this to an earlier check, we won't call pruneEmptySuites
unless there is a filter set.
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 a good start on this behavior. If we prune empty suites, we will end up with different run results in run
mode which will remove the reporting of these empty suites to the dashboard. Even though there is nothing to "run" it does help visually indicate that a test has been stubbed out.
I do think it's confusing we have differences between empty suites vs empty test behaviors in the reporter visuals, but seems like a small fix in the reporter to show those consistently if we wanted to go that route.
cy.get('@debugDismiss').should('have.been.called') | ||
cy.percySnapshot() | ||
}) | ||
}) |
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.
can we add more test cases when UI is squished and when there are large test cases to ensure the layout is as expected?
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 test takes a curated runnables
fixture. I could setup another fixture for filtered runnables but not sure what that would be testing since the filtering logic isn't being executed in this test.
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.
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.
Added a test with snapshots for 4 different reporter widths to demonstrate wrapping
For reference, here's the wrapping behavior:
Screen.Recording.2023-01-10.at.3.42.00.PM.mov
@@ -50,6 +50,7 @@ export class RunnablesStore { | |||
* content: RunnableArray | |||
*/ | |||
@observable runnablesHistory: Record<string, RunnableArray> = {} | |||
@observable totalRunnables: number = 0 |
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'm not sure this needs to be observable. I'd expect this to be static.
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 was under the assumption that it needs to be observable
so it triggers a rerender on change. Though it is static once the run is parsed, the store is updated later
} | ||
|
||
const normalizeAll = (suite, initialTests = {}, setTestsById, setTests, getRunnableId, getHookId, getOnlyTestId, getOnlySuiteId, createEmptyOnlyTest, incrementFoundTestsBy) => { | ||
pruneEmptySuites(suite, incrementFoundTestsBy) |
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.
Can we add some unit tests around this behavior? https://github.com/cypress-io/cypress/blob/3925ae062a9b9dca4cc9d61391ce5f6ff0525a8b/packages/driver/cypress/e2e/cypress/runner.cy.js
Co-authored-by: Emily Rohrbough <emilyrohrbough@users.noreply.github.com>
@emilyrohrbough I've addressed your feedback. I refactored |
…-debug-failures-filter
@@ -393,7 +397,7 @@ export class EventManager { | |||
|
|||
this.studioStore.initialize(config, runState) | |||
|
|||
const runnables = Cypress.runner.normalizeAll(runState.tests, hideCommandLog) | |||
const runnables = Cypress.runner.normalizeAll(runState.tests, hideCommandLog, testFilter) |
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.
Since this value is set in the specStore, any reason it's not directly pulled from here vs passing this around? useSpecStore.testFilter
?
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'm not seeing anything that stands out to make me think this won't work correctly but have you by chance verified these filter changes with a spec that changes domains via cy.visit()
mid-test? This would re-initialize Cypress mid-test and restore the reporter UI at the correct test index.
} | ||
|
||
const normalizeAll = (suite, initialTests = {}, testFilter, setTestsById, setTests, getRunnableId, getHookId, getOnlyTestId, getOnlySuiteId, createEmptyOnlyTest) => { | ||
let totalUnfilteredTests = 0 |
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.
should this default value be the total number of tests in the spec, not zero?
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.
Thought looking at the UI, is this meant to represent the totalFilteredTests?
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 believe Zach structured it this way to simplify accounting for filtered nested runnables (eg .only
and "skipped due to browser" conditions). Was easier to sum up matching elements than it was to deduct non-matches
The variable naming appears correct to me - it's used for the "total" value in the output of Header.tsx
(x/y tests
)
if (testFilter.includes(fullTitle)) { | ||
tests.push(test) | ||
|
||
if (rootSuite._onlyTests.includes(test)) { |
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.
we have quite a bit of logic to filter the "only" suites/tests later on in the normalize func. Is this needed here or can we rely on the existing filtering logic? if it is needed, do you know whats missing in the existing logic?
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 preemptively filters out any runnables marked with .only
that don't match the test filter before we get too far into parsing. We want the filter to take precedence, and without this we could end up including some tests that don't match the filter if they're marked with .only
.
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.
We are deleting the runnable references that don't match, I am surprised we are seeing tests incorrectly being included later on...
* Use specStore instead of passing `testFilter` as a param * Make type readonly
* Add test for responsive behavior of reporter panel with filter * Add test to validate filter is maintained across `cy.domain` reinitialization
@warrensplayer @marktnoonan @emilyrohrbough All feedback thus far has been addressed save adding some unit tests for |
I'm really struggling to find a way to do more granular unit tests for the |
Merging this to the feature branch. Added new issue to address additional testing here: #25485 |
User facing changelog
na
Additional details
Allows the filtering of tests in the runner based on the debug run results driven by the
runId
query parameter.This PR introduces the concept of a
Cypress.testFilter
that will allow the filtering of tests based on their titlePaths. For example,describe('s1', () => { it('t1', () => {}) })
would result in a titlePath ofs1 t1
. IfCypress.testFilter = ['s1 t1']
, only this test fill be processed by mocha. Currently it only supports an array of titlePaths to filter by, but I could see this expanding in the future to cb/regex.If there is an active
Cypress.testFilter
, tests that are not included behave like they never existed (pruned from the parsed suite/tests). If there is a suite/test.only
that does not contain a matching test it will not influence the tests that matched. If there is a suite/test.only
that is included, it will behave as normal.Steps to test
The query for what tests are filtered is stubbed for now, so you can modify these results inside
coreDataShape.ts
. To apply the filter, click on a spec then add&runId=123
to the url and refresh the page.How has the user experience changed?
Screen.Recording.2023-01-03.at.2.18.22.PM.mov
PR Tasks
cypress-documentation
?type definitions
?