Testing how it should be
Open slides: https://docs.google.com/presentation/d/1GkosEbTLBNZM4n9g9FXDBsSTl1D2IzcHePplTZ3PRwI/edit?usp=sharing
Setup the webapp
:
- cd webapp
npm install
npm start
Setup e2e
:
- cd e2e
npm install
npm start
Open up code:
- code .
Open localhost:8080
--- SLIDE ---
- Show the test runner
- Show package.json + deps + scripts, tsconfig.json, plugins/index.js, integration/example.ts
cy.visit('http://www.google.com');
cy.visit('http://www.google.com');
cy.get('.gLFyf').type('Hello world{enter}');
-
Worth pointing out commands follow user interactions. E.g.
.type
inserts text like a user would do it, key by key, this also results in less flake. -
One big idea that makes it possible is the command - execution speration.
console.log('start');
cy.visit('http://www.google.com');
console.log('between');
cy.get('.gLFyf').type('Hello world');
console.log('end');
- Advantages
- Implicit retries. Notice the retry, and notice the error occurs in the get, not in a
.type
cannot be invoked on undefined.
- Implicit retries. Notice the retry, and notice the error occurs in the get, not in a
cy.get('.somethingelse').type('Hello world')
- Second it allows you to write commands in a synchronous fashion even though they execute asynchronously, keeping you away from chaining hell or missed await calls.
All assertions are done with the should
command.
cy.visit('http://www.google.com')
cy.get('.gLFyf').should('have.value', 'h')
Notice the autocomplete, and once again touch on our flake resistance notice that its actively waiting for the value to arrive at that state, and we can interact with the ui ourself in this case to show how it passes once the value is actually that.
----- SLIDE -------
- Show the app running
- Add, remove, mark as complete - filter. Preserved on reload.
Lets write our first test to see it in practice. We already have the application running on 8080 and we can test that.
cy.visit('http://localhost:8080');
- At this point writing clear dependable tests is problematic as the initial state of the applicaiton will vary depending upon the backend state.
----- SLIDE -------
Notice that we start with some todos in there. Its not ideal, instead we should mock out the respose from this get-all
api. See the response we are getting right now. And lets remove it starting a cypress server
:
cy.server()
.route('GET', 'http://localhost:3000/api/get-all', { todos: [] });
// continue with cy.visit
We can move it to before each
beforeEach(() => {
cy.server().route('GET', 'http://localhost:3000/api/get-all', { todos: [] });
});
If we play around with the ui we see there are calls to POST``/add
and PUT``/set-all
. We could start mocking all of these all well and at that point you will be re-creating your backend. So think about it for a second and you will realize that the whole objective here is to ensure that the application starts in a known good state. So what you really want is init
the server instead of starting to mock these out.
cy.request('PUT', 'http://localhost:3000/api/set-all', { todos: [] });
These are true E2E tests.
And we can easily move it into its own file utils/server.ts
file. And then call it in a beforeEach
block in our tests.
import { startServer } from "../utils/server";
beforeEach(() => {
startServer();
});
----- SLIDE -------
-
Show playground to show css classes, selectors for links etc.
-
cypress/utils/page/todoPage
-
Selectors, actions.
-
add to test to show
beforeEach(() => {
startServer();
page.visit();
});
- Test :
cy.get(page.selectors.newTodoInput).type('Hello world{enter}');
- Even better:
page.addTodo('Hello world');
---- SLIDE: Behaviour vs. spec ----