Skip to content

Commit a7b57be

Browse files
authored
Docs: Edit style-guides (part 12 of doc improvement project) (grafana#92263)
* Docs: Edit style-guides (part 12) * Update contribute/style-guides/storybook.md * Prettier fixes
1 parent 8f807a2 commit a7b57be

File tree

4 files changed

+105
-83
lines changed

4 files changed

+105
-83
lines changed
Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
1-
# end-to-end tests for plugins
1+
# End-to-end tests for plugins
22

3-
When end-to-end testing Grafana plugins, it's recommended to use the [`@grafana/plugin-e2e`](https://www.npmjs.com/package/@grafana/plugin-e2e?activeTab=readme) testing tool. `@grafana/plugin-e2e` extends [`@playwright/test`](https://playwright.dev/) capabilities with relevant fixtures, models, and expect matchers; enabling comprehensive end-to-end testing of Grafana plugins across multiple versions of Grafana. For information on how to get started with Plugin end-to-end testing and Playwright, checkout the [Get started](https://grafana.com/developers/plugin-tools/e2e-test-a-plugin/get-started) guide.
3+
When end-to-end testing Grafana plugins, a best practice is to use the [`@grafana/plugin-e2e`](https://www.npmjs.com/package/@grafana/plugin-e2e?activeTab=readme) testing tool. The `@grafana/plugin-e2e` tool extends [`@playwright/test`](https://playwright.dev/) capabilities with relevant fixtures, models, and expect matchers. Use it to enable comprehensive end-to-end testing of Grafana plugins across multiple versions of Grafana.
44

5-
## Adding end-to-end tests for a core plugin
5+
> **Note:** To learn more, refer to our documentation on [plugin development](https://grafana.com/developers/plugin-tools/) and [end-to-end plugin testing](https://grafana.com/developers/plugin-tools/e2e-test-a-plugin/get-started).
66
7-
Playwright end-to-end tests for plugins should be added to the [`e2e/plugin-e2e`](https://github.com/grafana/grafana/tree/main/e2e/plugin-e2e) directory.
7+
## Add end-to-end tests for a core plugin
88

9-
1. Add a new directory that has the name as your plugin [`here`](https://github.com/grafana/grafana/tree/main/e2e/plugin-e2e). This is where your plugin tests will be kept.
9+
You can add Playwright end-to-end tests for plugins to the [`e2e/plugin-e2e`](https://github.com/grafana/grafana/tree/main/e2e/plugin-e2e) directory.
1010

11-
2. Playwright uses [projects](https://playwright.dev/docs/test-projects) to logically group tests together. All tests in a project share the same configuration.
12-
In the [Playwright config file](https://github.com/grafana/grafana/blob/main/playwright.config.ts), add a new project item. Make sure the `name` and the `testDir` sub directory matches the name of the directory that contains your plugin tests.
13-
Adding `'authenticate'` to the list of dependencies and specifying `'playwright/.auth/admin.json'` as storage state will ensure all tests in your project will start already authenticated as an admin user. If you wish to use a different role for and perhaps test RBAC for some of your tests, please refer to the plugin-e2e [documentation](https://grafana.com/developers/plugin-tools/e2e-test-a-plugin/use-authentication).
11+
1. Add a new directory that has the name as your plugin [`here`](https://github.com/grafana/grafana/tree/main/e2e/plugin-e2e). This is the directory where your plugin tests will be kept.
12+
13+
1. Playwright uses [projects](https://playwright.dev/docs/test-projects) to logically group tests together. All tests in a project share the same configuration.
14+
In the [Playwright config file](https://github.com/grafana/grafana/blob/main/playwright.config.ts), add a new project item. Make sure the `name` and the `testDir` subdirectory match the name of the directory that contains your plugin tests.
15+
Add `'authenticate'` to the list of dependencies and specify `'playwright/.auth/admin.json'` as the storage state to ensure that all tests in your project will start already authenticated as an admin user. If you want to use a different role for and perhaps test RBAC for some of your tests, refer to our [documentation](https://grafana.com/developers/plugin-tools/e2e-test-a-plugin/use-authentication).
1416

1517
```ts
1618
{
@@ -24,14 +26,16 @@ Playwright end-to-end tests for plugins should be added to the [`e2e/plugin-e2e`
2426
},
2527
```
2628

27-
3. Update the [CODEOWNERS](https://github.com/grafana/grafana/blob/main/.github/CODEOWNERS/#L315) file so that your team is owner of the tests in the directory you added in step 1.
29+
1. Update the [CODEOWNERS](https://github.com/grafana/grafana/blob/main/.github/CODEOWNERS/#L315) file so that your team is owner of the tests in the directory you added in step 1.
2830

2931
## Commands
3032

31-
- `yarn e2e:playwright` will run all Playwright tests. Optionally, you can provide the `--project mysql` argument to run tests in a certain project.
33+
- `yarn e2e:playwright` runs all Playwright tests. Optionally, you can provide the `--project mysql` argument to run tests in a specific project.
34+
35+
The `yarn e2e:playwright` script assumes you have Grafana running on `localhost:3000`. You may change this with an environment variable:
3236

33-
The script above assumes you have Grafana running on `localhost:3000`. You may change this by providing environment variables.
37+
`HOST=127.0.0.1 PORT=3001 yarn e2e:playwright`
3438

35-
`HOST=127.0.0.1 PORT=3001 yarn e2e:playwright`
39+
The `yarn e2e:playwright:server` starts a Grafana [development server](https://github.com/grafana/grafana/blob/main/scripts/grafana-server/start-server) on port 3001 and runs the Playwright tests.
3640

37-
- `yarn e2e:playwright:server` will start a Grafana [development server](https://github.com/grafana/grafana/blob/main/scripts/grafana-server/start-server) on port 3001 and run the Playwright tests. The development server is provisioned with the [devenv](https://github.com/grafana/grafana/blob/main/contribute/developer-guide.md#add-data-sources) dashboards, data sources and apps.
41+
- You can provision the development server with the [devenv](https://github.com/grafana/grafana/blob/main/contribute/developer-guide.md#add-data-sources) dashboards, data sources, and apps.

contribute/style-guides/e2e.md

Lines changed: 36 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
1-
# End-to-End tests
1+
# End-to-end tests
22

33
Grafana Labs uses a minimal [homegrown solution](../../e2e/utils/index.ts) built on top of [Cypress](https://cypress.io) for its end-to-end (E2E) tests.
44

55
Important notes:
66

77
- We generally store all element identifiers ([CSS selectors](https://mdn.io/docs/Web/CSS/CSS_Selectors)) within the framework for reuse and maintainability.
88
- We generally do not use stubs or mocks as to fully simulate a real user.
9-
- Cypress' promises [do not behave as you'd expect](https://docs.cypress.io/guides/core-concepts/introduction-to-cypress.html#Mixing-Async-and-Sync-code).
10-
- [Testing core Grafana](e2e-core.md) is different than [testing plugins](e2e-plugins.md) - core Grafana uses Cypress whereas plugins use [Playwright test](https://playwright.dev/).
9+
- Cypress' promises [don't behave as you might expect](https://docs.cypress.io/guides/core-concepts/introduction-to-cypress.html#Mixing-Async-and-Sync-code).
10+
- [Testing core Grafana](e2e-core.md) is different than [testing plugins](e2e-plugins.md)core Grafana uses Cypress whereas plugins use [Playwright test](https://playwright.dev/).
1111

1212
## Framework structure
1313

14-
Inspired by https://martinfowler.com/bliki/PageObject.html
14+
Our framework structure is inspired by [Martin Fowler's Page Object](https://martinfowler.com/bliki/PageObject.html).
1515

16-
- `Selector`: A unique identifier that is used from the E2E framework to retrieve an element from the Browser
17-
- `Page`: An abstraction for an object that contains one or more `Selectors` with `visit` function to navigate to the page.
18-
- `Component`: An abstraction for an object that contains one or more `Selectors` but without `visit` function
19-
- `Flow`: An abstraction that contains a sequence of actions on one or more `Pages` that can be reused and shared between tests
16+
- **`Selector`**: A unique identifier that is used from the E2E framework to retrieve an element from the browser
17+
- **`Page`**: An abstraction for an object that contains one or more `Selector` identifiers with the `visit` function to go to the page.
18+
- **`Component`**: An abstraction for an object that contains one or more `Selector` identifiers but without the `visit` function
19+
- **`Flow`**: An abstraction that contains a sequence of actions on one or more `Page` abstractions that can be reused and shared between tests
2020

2121
## Basic example
2222

@@ -26,13 +26,15 @@ Let's start with a simple [JSX](https://reactjs.org/docs/introducing-jsx.html) e
2626
<input className="gf-form-input login-form-input" type="text" />
2727
```
2828

29-
We _could_ target the field with a CSS selector 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 `data-testid` attributes as our preferred way of defining selectors. See [Aria-Labels vs data-testid](#aria-labels-vs-data-testid) for more details.
29+
It is possible to target the field with a CSS selector like `.gf-form-input.login-form-input`. However, doing so is a brittle solution because style changes occur frequently.
30+
31+
Furthermore, there is nothing that signals to future developers that this input is part of an E2E test. At Grafana, we use `data-testid` attributes as our preferred way of defining selectors. See [Aria-Labels vs data-testid](#aria-labels-vs-data-testid) for more details.
3032

3133
```jsx
3234
<input data-testid="Username input field" className="gf-form-input login-form-input" type="text" />
3335
```
3436

35-
The next step is to create a `Page` representation in our E2E 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:
37+
The next step is to create a `Page` representation in our E2E framework. Doing so glues the test with the real implementation using the `pageFactory` function. For that function we can supply a `url` and selector like in the following example:
3638

3739
```typescript
3840
export const Login = {
@@ -43,9 +45,9 @@ export const Login = {
4345
};
4446
```
4547

46-
Note that the selector is prefixed with `data-testid` - this is a signal to the framework to look for the selector in the `data-testid` attribute.
48+
In this example, the selector is prefixed with `data-testid`. The prefix is a signal to the framework to look for the selector in the `data-testid` attribute.
4749

48-
The next step is to add the `Login` page to the `Pages` export within [_\<repo-root>/packages/grafana-e2e-selectors/src/selectors/pages.ts_](../../packages/grafana-e2e-selectors/src/selectors/pages.ts) so that it appears when we type `e2e.pages` in our IDE.
50+
The next step is to add the `Login` page to the `Pages` export within [_\<repo-root>/packages/grafana-e2e-selectors/src/selectors/pages.ts_](../../packages/grafana-e2e-selectors/src/selectors/pages.ts) so that it appears when we type `e2e.pages` in your IDE.
4951

5052
```typescript
5153
export const Pages = {
@@ -56,7 +58,9 @@ export const Pages = {
5658
};
5759
```
5860

59-
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.
61+
Now that we have a page called `Login` in our `Pages` const, use it to add a selector in our HTML as shown in the following example. This page really signals to future developers that it is part of an E2E test.
62+
63+
Example:
6064

6165
```jsx
6266
import { selectors } from '@grafana/e2e-selectors';
@@ -66,9 +70,8 @@ import { selectors } from '@grafana/e2e-selectors';
6670

6771
The last step in our example is to use our `Login` page as part of a test.
6872

69-
- The `url` property is used whenever we call the `visit` function and is equivalent to the Cypress' [`cy.visit()`](https://docs.cypress.io/api/commands/visit.html#Syntax).
70-
71-
- Any defined selector can be accessed from the `Login` page by invoking it. This is equivalent to the result of the Cypress function [`cy.get(…)`](https://docs.cypress.io/api/commands/get.html#Syntax).
73+
- Use the `url` property whenever you call the `visit` function. It is equivalent to the [`cy.visit()`](https://docs.cypress.io/api/commands/visit.html#Syntax) in Cypress.
74+
- Access any defined selector from the `Login` page by invoking it. This is equivalent to the result of the Cypress function [`cy.get(…)`](https://docs.cypress.io/api/commands/get.html#Syntax).
7275

7376
```typescript
7477
describe('Login test', () => {
@@ -83,7 +86,7 @@ describe('Login test', () => {
8386

8487
## Advanced example
8588

86-
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.
89+
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, there's a list of data sources that we want to click on during an E2E test.
8790

8891
```jsx
8992
<ul>
@@ -97,7 +100,7 @@ Let's take a look at an example that uses the same `selector` for multiple items
97100
</ul>
98101
```
99102

100-
Just as before in the basic example we'll start by creating a page abstraction using the `pageFactory` function:
103+
Like in the basic example, start by creating a page abstraction using the `pageFactory` function:
101104

102105
```typescript
103106
export const DataSources = {
@@ -106,11 +109,11 @@ export const DataSources = {
106109
};
107110
```
108111

109-
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.
112+
You might have noticed that instead of a simple string as the selector, there's a function that takes a string parameter as an argument and returns a formatted string using the argument.
110113

111-
Just as before we need to add the `DataSources` page to the exported const `Pages` in `packages/grafana-e2e-selectors/src/selectors/pages.ts`.
114+
Just as before, you need to add the `DataSources` page to the exported const `Pages` in `packages/grafana-e2e-selectors/src/selectors/pages.ts`.
112115

113-
The next step is to use the `dataSources` selector function as in our example below:
116+
The next step is to use the `dataSources` selector function as in the following example:
114117

115118
```jsx
116119
<ul>
@@ -126,15 +129,15 @@ The next step is to use the `dataSources` selector function as in our example be
126129
</ul>
127130
```
128131

129-
When this list is rendered with the data sources with names `A`, `B` and `C` ,the resulting HTML would look like:
132+
When this list is rendered with the data sources with names `A`, `B` and `C` ,the resulting HTML looks like this:
130133

131134
```html
132135
<div class="card-item-name" data-testid="data-testid Data source list item A">A</div>
133136
<div class="card-item-name" data-testid="data-testid Data source list item B">B</div>
134137
<div class="card-item-name" data-testid="data-testid Data source list item C">C</div>
135138
```
136139

137-
Now we can write our test. The one thing that differs from the [basic example](#basic-example) above is that we pass in which data source we want to click on as an argument to the selector function:
140+
Now we can write our test. The one thing that differs from the previous [basic example](#basic-example) is that we pass in which data source we want to click as an argument to the selector function:
138141

139142
```typescript
140143
describe('List test', () => {
@@ -147,46 +150,44 @@ describe('List test', () => {
147150
});
148151
```
149152

150-
## Aria-Labels vs data-testid
153+
## aria-label versus data-testid
151154

152-
Our selectors are set up to work with both aria-labels and data-testid attributes. Aria-labels help assistive technologies such as screenreaders identify interactive elements of a page for our users.
155+
Our selectors are set up to work with both `aria-label` attributes and `data-testid` attributes. The `aria-label` attributes help assistive technologies such as screen readers identify interactive elements of a page for our users.
153156

154-
A good example of a time to use an aria-label might be if you have a button with an X to close:
157+
A good example of a time to use an aria-label might be if you have a button with an **X** to close:
155158

156159
```
157160
<button aria-label="close">X<button>
158161
```
159162

160-
It might be clear visually that the X closes the modal, but audibly it would not be clear for example.
163+
It might be clear visually that the **X** closes the modal, but audibly it would not be clear, for example.
161164

162165
```
163166
<button aria-label="close">Close<button>
164167
```
165168

166169
The example might read aloud to a user as "Close, Close" or something similar.
167170

168-
However adding aria-labels to elements that are already clearly labeled or not interactive can be confusing and redundant for users with assistive technologies.
171+
However, adding an aria-label to an element that is already clearly labeled or not interactive can be confusing and redundant for users with assistive technologies.
169172

170-
In such cases rather than adding unnecessary aria-labels to components so as to make them selectable for testing, it is preferable to use a data attribute that would not be read aloud with an assistive technology for example:
173+
In such cases, don't add an unnecessary aria-label to a component so as to make them selectable for testing. Instead, use a data attribute that will not be read aloud with an assistive technology. For example:
171174

172175
```
173176
<button data-testid="modal-close-button">Close<button>
174177
```
175178

176-
We have added support for this in our selectors, to use:
177-
178-
Prefix your selector string with "data-testid":
179+
We have added support for data attributes in our selectors Prefix your selector string with `data-testid`:
179180

180181
```typescript
181182
export const Components = {
182183
Login: {
183-
openButton: 'open-button', // this would look for an aria-label
184-
closeButton: 'data-testid modal-close-button', // this would look for a data-testid
184+
openButton: 'open-button', // this looks for an aria-label
185+
closeButton: 'data-testid modal-close-button', // this looks for a data-testid
185186
},
186187
};
187188
```
188189

189-
and in your component, import the selectors and add the data test id:
190+
and in your component, import the selectors and add the `data-testid`:
190191

191192
```
192193
<button data-testid={Selectors.Components.Login.closeButton}>

0 commit comments

Comments
 (0)