-
Notifications
You must be signed in to change notification settings - Fork 14.4k
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
Adding simple Cypress tests #5693
Adding simple Cypress tests #5693
Conversation
superset/assets/package.json
Outdated
@@ -16,7 +16,8 @@ | |||
"build": "webpack --mode=production --colors --progress", | |||
"lint": "eslint --ignore-path=.eslintignore --ext .js,.jsx .", | |||
"lint-fix": "eslint --fix --ignore-path=.eslintignore --ext .js,.jsx .", | |||
"sync-backend": "babel-node --presets env src/syncBackend.js" | |||
"sync-backend": "babel-node --presets env src/syncBackend.js", | |||
"cypress": "node_modules/.bin/cypress" |
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.
"cypress": "cypress"
npm run will resolve CLI installed under node_modules
cy.visit('/superset/dashboard/births'); | ||
|
||
cy.route('POST', '/superset/explore_json/**').as('getJson'); | ||
cy.wait(6000, ['@getJson']); |
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 a specific time to wait is usually not necessary, but there's currently an open issue to better handle multiple matched responses. For now this is the workaround: cypress-io/cypress#477
526ade8
to
40da12c
Compare
5b3636e
to
a325999
Compare
Codecov Report
@@ Coverage Diff @@
## master #5693 +/- ##
==========================================
+ Coverage 63.72% 63.77% +0.05%
==========================================
Files 367 367
Lines 23173 23205 +32
Branches 2603 2603
==========================================
+ Hits 14767 14799 +32
Misses 8391 8391
Partials 15 15
Continue to review full report at Codecov.
|
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.
Thank you for setting this up. Will be super useful!
xhrs.forEach((data) => { | ||
expect(data.status).to.eq(200); | ||
expect(data.response.body).to.have.property('error', null); | ||
cy.get(`#slice-container-${data.response.body.form_data.slice_id}`); |
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 does this line do?
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 would verify that there exists a slice container in the dashboard for each slice loaded.
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.
does it for sure fail in the case that it doesn't exist? is it better/safer to chain a .then()
with an expect?
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.
Yes a get will fail with CypressError: Timed out retrying: Expected to find element
the two expects above should always catch before the last one does, but seems good to have our bases covered.
cy.get(`#slice-container-${data.response.body.form_data.slice_id}`); | ||
}); | ||
}); | ||
cy.get('#app').then((data) => { |
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.
Perhaps this should be nested inside the then
block of cy.get('@getJson.all')
to ensure that sliceData
already has value when it reach this test.
If I understand correctly, both cy.get('@getJson.all')
and cy.get('#app')
are async so their then
blocks are not guaranteed to be in order, unless #app
will not exist until @getJson.all
resolve.
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.
Because there's a set time to wait on getJson
above, the other requests don't start until it's done (which would mean that both cy.get('@getJson.all')
and cy.get('#app')
don't execute until the 7000 milliseconds is up). In this special case it doesn't specifically matter for their blocks to be in order, but it does seem useful to change because it will be necessary in other cases.
optionName: 'metric_1de0s4viy5d_ly7y8k6ghvk', | ||
}]; | ||
|
||
const formData = Object.assign(FORM_DATA_DEFAULTS, metrics); |
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 be
Object.assign({}, FORM_DATA_DEFAULTS, metrics);
To create a copy and put all the fields of FORM_DATA_DEFAULTS
and metrics
into the new object.
The current code will modify FORM_DATA_DEFAULTS
.
You can also use this object spread notation
const formData = { ...FORM_DATA_DEFAULTS, ...metrics }
}); | ||
|
||
Cypress.Commands.add('visitChart', ({ name, sliceId, formData }) => { | ||
const baseUrl = '/superset/explore/?form_data='; |
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.
take it out of this function so this constant won't be created every time visitChart is called.
c34a28e
to
202e38b
Compare
@mistercrunch @graceguo-supercat @john-bodley Initial Cypress integration test PR is ready if you want to take a look! |
@mistercrunch @graceguo-supercat let me know if you have any thoughts about cypress integration 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.
LGTM
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.
Had a couple of suggested changes, but this looks great overall! Thanks for getting this all started 🚀
xhrs.forEach((data) => { | ||
expect(data.status).to.eq(200); | ||
expect(data.response.body).to.have.property('error', null); | ||
cy.get(`#slice-container-${data.response.body.form_data.slice_id}`); |
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.
does it for sure fail in the case that it doesn't exist? is it better/safer to chain a .then()
with an expect?
optionName: 'metric_1de0s4viy5d_ly7y8k6ghvk', | ||
}]; | ||
|
||
const formData = Object.assign({}, FORM_DATA_DEFAULTS, { metrics }); |
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 use object rest spread? or is this code not transpiled?
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.
Is there a reason object rest spread is better in this situation?
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.
because it's modern javascript and part of the airbnb best practices. I think we should strive to follow them, and we should have this lint rule enabled ... but we don't because we have a lot turned off which arguably should be on.
// -- This is will overwrite an existing command -- | ||
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) | ||
|
||
const BASE_URL = '/superset/explore/?form_data='; |
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.
is BASE_EXPLORE_URL
a better name since this is not used as the base for every request?
}); | ||
}); | ||
|
||
Cypress.Commands.add('visitChart', ({ name, sliceId, formData }) => { |
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.
when I first read this it wasn't clear to me what visitChart
meant. While it seems possibly useful to have a method that could handle 3 different types of input, since they are all mutually exclusive could we instead create 3 methods with more descriptive names which each expect one type of input?
@@ -84,6 +84,7 @@ export default class Control extends React.PureComponent { | |||
const divStyle = this.props.hidden ? { display: 'none' } : null; | |||
return ( | |||
<div | |||
data-test={this.props.name} |
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 be removed? I think it's not ideal to have props set solely for 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.
this was listed as a best practice by cypress to avoid using css selectors that may change https://docs.cypress.io/guides/references/best-practices.html#Selecting-Elements
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.
but we use css selectors elsewhere in these tests right? that seems unavoidable to some extent. If you feel strongly about it let's go with this pattern but perhaps you could set it to data-name
so it indicates what the value is rather than just '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.
It seemed ok to use some id / class selectors that are less likely to change. But when I google around for blogs discussing making UI tests resilient to change, this pattern comes up often.
This was a particular case where there was no identifier on the tag that was unique to it being a metric control (vs other type of control), so I needed to add some kind of id or identifier here, seemed good to be clear that it's just used for testing (so we know not to remove / change it). That's the reason I like using data_test
, but I can change it if you feel strongly.
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.
got it, thanks for more context. I'm cool with whatever you feel is best here 👍
1ef2108
to
e5906f5
Compare
I added Cypress to tox. It takes 12 minutes but most of that time is in setup. Let me know if anyone has other feedback. |
tox.ini
Outdated
@@ -74,4 +89,5 @@ envlist = | |||
flake8 | |||
javascript | |||
pylint | |||
cypress |
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.
Nit. Alphabetical order.
tox.ini
Outdated
-rrequirements.txt | ||
-rrequirements-dev.txt | ||
|
||
|
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.
Nit. Extra blank line.
tox.ini
Outdated
@@ -61,6 +63,19 @@ commands = | |||
{toxinidir}/superset/assets/js_build.sh | |||
deps = | |||
|
|||
[testenv:cypress] |
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.
Nit. Alphabetical order of test environments.
tox.ini
Outdated
commands = | ||
{toxinidir}/superset/assets/cypress_build.sh | ||
setenv = | ||
SUPERSET_CONFIG = tests.superset_test_config |
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.
Nit. Alphabetical order.
d463f53
to
f423f9c
Compare
f423f9c
to
562e6cf
Compare
* Adding simple Cypress tests * Changing visitChart into multiple commands * Adding Cypress to tox
* Adding simple Cypress tests * Changing visitChart into multiple commands * Adding Cypress to tox
I've started to create a few cypress tests and want to get some eyes on it and get the code out so that others can add tests if they'd like. I haven't added it to tox yet but I've run through the steps we'd need to take to run it from the cli.
The general structure for tests that I've included is:
Explore
Dashboard - testing dashboard loading and editing ability
The difference between control tests and visualization tests seems really minor, but I think it would be useful in thinking about testing in isolation. Removing/setting controls for a single viz type, as well as, validating complex controls like metrics and filters would be useful to separate from testing the parameters for each visualization types. Let me know if anyone has thoughts on this!
I needed to add
load_test_users
as a cli command so we can run it in backend and frontend tests. I'd want to check that we are in test mode before creating admin users, but it doesn't seem like there's an easy flag to test for test mode. Anyone know of a way to check this? I can add something if not.Here are the rough steps for running from cli