This commit is contained in:
notmd
2023-01-25 16:02:20 +07:00
parent 31630be319
commit 97cd57a300
10 changed files with 147 additions and 213 deletions
+40 -58
View File
@@ -1,24 +1,19 @@
# Component and e2e testing with Cypress
[Cypress](https://www.cypress.io/) is used for both component- and end-to-end
testing. Below there's a few examples for the context of this site. To learn
more, the
[Cypress documentation](https://docs.cypress.io/guides/getting-started/opening-the-app)
has it all.
[Cypress](https://www.cypress.io/) is used for both component- and end-to-end testing. Below there's a few examples for
the context of this site. To learn more, the
[Cypress documentation](https://docs.cypress.io/guides/getting-started/opening-the-app) has it all.
Don't get scared by the commercial offerings they offer. Their core is open
source, the cloud offering is not necesarry at all and can be replaced by CI
tooling and [community efforts](https://sorry-cypress.dev/).
Don't get scared by the commercial offerings they offer. Their core is open source, the cloud offering is not necesarry
at all and can be replaced by CI tooling and [community efforts](https://sorry-cypress.dev/).
# Component testing
To write a new component test, you either create a new `.tsx` adjacent to the
component you want to test or you can use the guide presented yo you when
running `npm run cypress` which allows you to easily create the skeleton test
for an existing component.
To write a new component test, you either create a new `.tsx` adjacent to the component you want to test or you can use
the guide presented yo you when running `npm run cypress` which allows you to easily create the skeleton test for an
existing component.
If you have a `Button.tsx` component, create a file next to it called
`Button.cy.tsx` which could look like this:
If you have a `Button.tsx` component, create a file next to it called `Button.cy.tsx` which could look like this:
```typescript
import React from "react";
@@ -35,28 +30,24 @@ describe("<Button />", () => {
## What's happening here?
First we use `cy.mount` to mount our component under test. Notive how we specify
`className` and inner text - this is where we arrange our component with fake
data that we could assert on later.
First we use `cy.mount` to mount our component under test. Notive how we specify `className` and inner text - this is
where we arrange our component with fake data that we could assert on later.
In the example above, we also use `cy.get` to select the rendered `button`
element. Cypress has multiple ways to
[select elements](https://docs.cypress.io/guides/references/best-practices),
`get` is just one of them (and often not recommended).
In the example above, we also use `cy.get` to select the rendered `button` element. Cypress has multiple ways to
[select elements](https://docs.cypress.io/guides/references/best-practices), `get` is just one of them (and often not
recommended).
At last, we use `captureSnapshot` which is a plugin that snaps a photo of the
`button` element and compares it to a baseline located in the
`./cypress-visual-screenshots/baseline/` folder. If there's too many unidentical
pixels between the two, it will fail the test.
At last, we use `captureSnapshot` which is a plugin that snaps a photo of the `button` element and compares it to a
baseline located in the `./cypress-visual-screenshots/baseline/` folder. If there's too many unidentical pixels between
the two, it will fail the test.
# End-to-end (e2e) testing
e2e tests are stored in the `./cypress/e2e` folder and should be named
`{page}.cy.ts` and located in a relative folder structure that mirrors the page
under test.
e2e tests are stored in the `./cypress/e2e` folder and should be named `{page}.cy.ts` and located in a relative folder
structure that mirrors the page under test.
When running `npm run cypress` and selecting e2e testing, we assume you have the
NextJS site running at `localhost:3000`.
When running `npm run cypress` and selecting e2e testing, we assume you have the NextJS site running at
`localhost:3000`.
An example test could look as follows:
@@ -74,39 +65,33 @@ export {};
## What's happening here?
First we use [`cy.visit`](https://docs.cypress.io/api/commands/visit) to point
the browser at the desired page. It appends relative paths to the configured
`baseUrl` (found in `./cypress.config.ts`).
First we use [`cy.visit`](https://docs.cypress.io/api/commands/visit) to point the browser at the desired page. It
appends relative paths to the configured `baseUrl` (found in `./cypress.config.ts`).
Cypress will
[automatically await](https://docs.cypress.io/guides/core-concepts/introduction-to-cypress#Timeouts)
almost anything you do, but fail if the default timeout is reached.
Cypress will [automatically await](https://docs.cypress.io/guides/core-concepts/introduction-to-cypress#Timeouts) almost
anything you do, but fail if the default timeout is reached.
Then we get the email input field and type our email address. We find the input
field using the data-cy attribute that we added in the source code of the
element on the page.
Then we get the email input field and type our email address. We find the input field using the data-cy attribute that
we added in the source code of the element on the page.
```jsx
<Input data-cy="email-address" placeholder="Email Address" />
```
Using `data-cy` is how we ensure that selecting the element is robust to changes
in page design or function and is one of the
Using `data-cy` is how we ensure that selecting the element is robust to changes in page design or function and is one
of the
[best practices recommended by Cypress](https://docs.cypress.io/guides/references/best-practices#Selecting-Elements).
Next we call `type()` to use the keyboard, cypress will automatically focus the
element and send the keypress events. Notice the `{enter}` keyword, this will
cause Cypress to hit the return key which we expect to submit the form.
Next we call `type()` to use the keyboard, cypress will automatically focus the element and send the keypress events.
Notice the `{enter}` keyword, this will cause Cypress to hit the return key which we expect to submit the form.
We then assert that the URL should contain `/auth/verify`. Again the timeout
will make sure we are not waiting forever, and the test will fail if we do not
manage to get there in a reasonable time.
We then assert that the URL should contain `/auth/verify`. Again the timeout will make sure we are not waiting forever,
and the test will fail if we do not manage to get there in a reasonable time.
## Authenticating in e2e tests
For end-to-end tests almost every test will need to first sign in to the
website. To make this easier we have a custom command for Cypress that makes
logging in with an email address a single command, `cy.signInWithEmail()`.
For end-to-end tests almost every test will need to first sign in to the website. To make this easier we have a custom
command for Cypress that makes logging in with an email address a single command, `cy.signInWithEmail()`.
```typescript
describe("replying as the assistant", () => {
@@ -115,16 +100,13 @@ describe("replying as the assistant", () => {
cy.visit("/create/assistant_reply");
cy.get('[data-cy="reply"').type(
"You need to run pre-commit to make the reviewer happy."
);
cy.get('[data-cy="reply"').type("You need to run pre-commit to make the reviewer happy.");
cy.get('[data-cy="submit"]').click();
});
});
```
In this example we sign in as `cypress@example.com` before visiting the
`/create/assistant_reply` page that is only available when authenticated. We can
then continue on with our test as normal. Note: using `cy.signInWithEmail()`
requires that the maildev is running, which should have been started as part of
the `docker compose up` command that is required to do any end-to-end testing.
In this example we sign in as `cypress@example.com` before visiting the `/create/assistant_reply` page that is only
available when authenticated. We can then continue on with our test as normal. Note: using `cy.signInWithEmail()`
requires that the maildev is running, which should have been started as part of the `docker compose up` command that is
required to do any end-to-end testing.
+1 -4
View File
@@ -7,9 +7,6 @@ describe("<Container />", () => {
const className = "my-class";
const text = "test_container";
cy.mount(<Container className={className}>{text}</Container>);
cy.get(`div.${className}`)
.should("have.class", className)
.should("be.visible")
.should("contain", text);
cy.get(`div.${className}`).should("have.class", className).should("be.visible").should("contain", text);
});
});
@@ -17,7 +17,7 @@ describe("Contract test for Oasst API", function () {
it("can ack a task", async () => {
const task = await oasstApiClient.fetchTask("random", testUser, "en");
expect(await oasstApiClient.ackTask(task.id, "321")).to.be.undefined;
expect(await oasstApiClient.ackTask(task.id, "321")).to.be.null;
});
it("can record a taskInteraction", async () => {
+1 -3
View File
@@ -56,9 +56,7 @@ describe("handles random tasks", () => {
break;
}
case undefined: {
throw new Error(
"No tasks available, but at least create initial prompt expected"
);
throw new Error("No tasks available, but at least create initial prompt expected");
}
default:
throw new Error(`Unexpected task type: ${type}`);
+5 -10
View File
@@ -37,19 +37,14 @@
// }
Cypress.Commands.add("signInUsingEmailedLink", (emailAddress) => {
const mailDevApi = `${Cypress.env("MAILDEV_PROTOCOL")}://${Cypress.env(
"MAILDEV_HOST"
)}:${Cypress.env("MAILDEV_API_PORT")}`;
cy.request(
"GET",
`${mailDevApi}/email?headers.to=${emailAddress.toLowerCase()}`
).then((response) => {
const mailDevApi = `${Cypress.env("MAILDEV_PROTOCOL")}://${Cypress.env("MAILDEV_HOST")}:${Cypress.env(
"MAILDEV_API_PORT"
)}`;
cy.request("GET", `${mailDevApi}/email?headers.to=${emailAddress.toLowerCase()}`).then((response) => {
const emails = response.body;
// Find and use login link
const loginLink = emails
.pop()
.html.match(/href="[^"]+(\/api\/auth\/callback\/[^"]+?)"/)[1];
const loginLink = emails.pop().html.match(/href="[^"]+(\/api\/auth\/callback\/[^"]+?)"/)[1];
cy.visit(loginLink);
});
});