From 1774b8f7e9b0b5d7bd41763922da2ef0b8837330 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?= Date: Tue, 10 Dec 2019 09:49:12 +0100 Subject: [PATCH] Docs: Updates from puppeteer to Cypress (#20962) * Docs: Updates from puppeteer to Cypress * e2e: Adds documentation * Docs: Adds link to e2e framework from contributing * Docs: Updates after PR comments * Update contribute/style-guides/e2e.md Co-Authored-By: Arve Knudsen * Update contribute/style-guides/e2e.md Co-Authored-By: Arve Knudsen * Update contribute/style-guides/e2e.md Co-Authored-By: Arve Knudsen * Update contribute/style-guides/e2e.md Co-Authored-By: Arve Knudsen * Update contribute/style-guides/e2e.md Co-Authored-By: Arve Knudsen * Update contribute/style-guides/e2e.md Co-Authored-By: Diana Payton <52059945+oddlittlebird@users.noreply.github.com> * Update contribute/style-guides/e2e.md Co-Authored-By: Arve Knudsen * Update contribute/style-guides/e2e.md Co-Authored-By: Diana Payton <52059945+oddlittlebird@users.noreply.github.com> * Docs: Breaks a long sentance in two --- contribute/developer-guide.md | 4 +- contribute/style-guides/e2e.md | 140 +++++++++++++++++++++++++++++++++ 2 files changed, 143 insertions(+), 1 deletion(-) create mode 100644 contribute/style-guides/e2e.md diff --git a/contribute/developer-guide.md b/contribute/developer-guide.md index c4d8771ef5d..9ff168cb377 100644 --- a/contribute/developer-guide.md +++ b/contribute/developer-guide.md @@ -94,7 +94,9 @@ go test -v ./pkg/... ### Run end-to-end tests -The end-to-end tests in Grafana uses [puppeteer](https://github.com/GoogleChrome/puppeteer) to run automated scripts in a headless Chrome browser. To run the tests: +The end to end tests in Grafana use [Cypress](https://www.cypress.io/) to run automated scripts in a headless Chromium browser. Read more about our [e2e framework](/contribute/style-guides/e2e.md). + +To run the tests: ``` yarn e2e-tests diff --git a/contribute/style-guides/e2e.md b/contribute/style-guides/e2e.md new file mode 100644 index 00000000000..c0e0446db37 --- /dev/null +++ b/contribute/style-guides/e2e.md @@ -0,0 +1,140 @@ +# End to end test framework +Grafana Labs uses a minimal home grown solution built on top of Cypress for our end to end (e2e) tests. + +## Basic concepts +Here is a good introduction to e2e best practices: https://martinfowler.com/bliki/PageObject.html. +- `Selector`: A unique identifier that is used from the e2e framework to retrieve an element from the Browser +- `Page`: An abstraction for an object that contains one or more `Selectors` +- `Flow`: An abstraction that contains a sequence of actions on one or more `Pages` that can be reused and shared between tests + +## Basic example +Let's start with a simple example with a single selector. For simplicity, all examples are in JSX. + +In our example app, we have an input that we want to type some text into during our e2e test. +```jsx harmony +
+ +
+``` + +We could define a selector using `JQuery` [type selectors](https://api.jquery.com/category/selectors/) with a string like `'.gf-form-input.login-form-input'` but that would be brittle as style changes occur frequently. Furthermore there is nothing that signals to future developers that this input is part of an e2e test. + +At Grafana, we use `aria-label` as our preferred way of defining selectors instead of `data-*` attributes. This also aids in accessibility. +Let's add a descriptive `aria-label` to our simple example. +```jsx harmony +
+ +
+``` + +Now that we added the `aria-label` we suddenly get more information about this particular field. It's an input field that represents a username, but there it's still not really signaling that it's part of an e2e test. + +The next step is to create a `Page` representation in our e2e test framework to glue the test with the real implementation using the `pageFactory` function. For that function we can supply a `url` and `selectors` like in the example below: +```typescript +export const Login = pageFactory({ + url: '/login', // used when called from Login.visit() + selectors: { + username: 'Username input field', // used when called from Login.username().type('Hello World') + }, +}); +``` + +The next step is to add the `Login` page to the exported const `Pages` in `packages/grafana-e2e/src/pages/index.ts` so that it appears when we type `e2e.pages` in our IDE. +```ecmascript 6 +export const Pages = { + Login, + ..., + ..., + ..., +}; + +``` +Now that we have a `Page` called `Login` in our `Pages` const we can use that to add a selector in our html like shown below and now this really signals to future developers that it is part of an e2e test. +```jsx harmony +
+ +
+``` + +The last step in our example is to use our `Login` page as part of a test. The `pageFactory` function we used before gives us two things: +- The `url` property is used whenever we call the `visit` function and is equivalent to the Cypress function [cy.visit()](https://docs.cypress.io/api/commands/visit.html#Syntax). +- Any defined selector in the `selectors` property can be accessed from the `Login` page by invoking it and this is equivalent to the result of the Cypress function [cy.get(...)](https://docs.cypress.io/api/commands/get.html#Syntax). +```ecmascript 6 +describe('Login test', () => { + it('Should pass', () => { + e2e.pages.Login.visit(); + e2e.pages.Login.username().type('admin'); + }); +}); +``` + +## Advanced example +Let's take a look at an example that uses the same `selector` for multiple items in a list for instance. In this example app we have a list of data sources that we want to click on during an e2e test. + +```jsx harmony + +``` +``` + +Just as before in the basic example we'll start by creating a page abstraction using the `pageFactory` function: +```typescript +export const DataSources = pageFactory({ + url: '/datasources', + selectors: { + dataSources: (dataSourceName: string) => `Data source list item ${dataSourceName}`, + }, +}); +``` +You might have noticed that instead of a simple `string` as the `selector`, we're using a `function` that takes a string parameter as an argument and returns a formatted string using the argument. + +Just as before we need to add the `DataSources` page to the exported const `Pages` in `packages/grafana-e2e/src/pages/index.ts`. + +The next step is to use the `dataSources` selector function as in our example below: +```jsx harmony + +``` + +When this list is rendered with the data sources with names `A`, `B`, `C` the resulting html would become: +```jsx harmony +
+ A +
+... +
+ B +
+... +
+ C +
+``` + +Now we can go ahead and write our test and the one thing that differs from the `Basic example` is that we pass in which data source we want to click on as an argument to the selector function: +```ecmascript 6 +describe('List test', () => { + it('Clicking on data source named B', () => { + e2e.pages.DataSources.visit(); + e2e.pages.DataSources.dataSources('B').click(); + }); +}); +```