Skip to content

Commit

Permalink
feat: add prefer-user-facing-locators rule
Browse files Browse the repository at this point in the history
  • Loading branch information
Haberkamp committed Aug 25, 2023
1 parent aca762e commit 7b9ec83
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 0 deletions.
26 changes: 26 additions & 0 deletions docs/rules/prefer-user-facing-locators.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
## Disallow using `page.locator` (`no-raw-selector`)

Prefer using user-facing locators over `page.locator` to make tests more robust.

Check out the [Playwright documentation](https://playwright.dev/docs/locators)
for more information.

## Rule Details

Example of **incorrect** code for this rule:

```javascript
await page.locator('button').click();
```

Example of **correct** code for this rule:

```javascript
await page.getByRole('button').click();
```

```javascript
await page.getByRole('button', {
name: 'Submit',
});
```
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import noNestedStep from './rules/no-nested-step';
import noNetworkidle from './rules/no-networkidle';
import noNthMethods from './rules/no-nth-methods';
import noPagePause from './rules/no-page-pause';
import noRawSelector from './rules/no-raw-selector';
import noRestrictedMatchers from './rules/no-restricted-matchers';
import noSkippedTest from './rules/no-skipped-test';
import noUselessAwait from './rules/no-useless-await';
Expand Down Expand Up @@ -102,6 +103,7 @@ export = {
'no-networkidle': noNetworkidle,
'no-nth-methods': noNthMethods,
'no-page-pause': noPagePause,
'no-raw-selector': noRawSelector,
'no-restricted-matchers': noRestrictedMatchers,
'no-skipped-test': noSkippedTest,
'no-useless-await': noUselessAwait,
Expand Down
30 changes: 30 additions & 0 deletions src/rules/no-raw-selector.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Rule } from 'eslint';
import { getStringValue, isPageMethod } from '../utils/ast';

export default {
create(context) {
return {
CallExpression(node) {
if (node.callee.type !== 'MemberExpression') return;
const method = getStringValue(node.callee.property);

if (isPageMethod(node, 'locator') || method === 'locator') {
context.report({ messageId: 'noRawSelector', node });
}
},
};
},
meta: {
docs: {
category: 'Best Practices',
description: 'Disallows the usage of raw selectors',
recommended: false,
url: 'https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-raw-selector.md',
},
messages: {
noRawSelector:
'Usage of raw selector detected. Use methods like .getByRole() or .getByText() instead of raw selectors.',
},
type: 'suggestion',
},
} as Rule.RuleModule;
65 changes: 65 additions & 0 deletions test/spec/no-raw-selector.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import rule from '../../src/rules/no-raw-selector';
import { runRuleTester, test } from '../utils/rule-tester';

const messageId = 'noRawSelector';

runRuleTester('no-raw-selector', rule, {
invalid: [
{
code: test('await page.locator()'),
errors: [{ column: 34, endColumn: 48, line: 1, messageId }],
},
{
code: test('await this.page.locator()'),
errors: [{ column: 34, endColumn: 53, line: 1, messageId }],
},
{
code: test("await page.locator('.btn')"),
errors: [{ column: 34, endColumn: 54, line: 1, messageId }],
},
{
code: test('await page["locator"](".btn")'),
errors: [{ column: 34, endColumn: 57, line: 1, messageId }],
},
{
code: test('await page[`locator`](".btn")'),
errors: [{ column: 34, endColumn: 57, line: 1, messageId }],
},

{
code: test('await frame.locator()'),
errors: [{ column: 34, endColumn: 49, line: 1, messageId }],
},

{
code: test(
'const section = await page.getByRole("section"); section.locator(".btn")'
),
errors: [{ column: 77, endColumn: 100, line: 1, messageId }],
},
],
valid: [
test('await page.click()'),
test('await this.page.click()'),
test('await page["hover"]()'),
test('await page[`check`]()'),

// Preferred user facing locators
test('await page.getByText("lorem ipsum")'),
test('await page.getByLabel(/Email/)'),
test('await page.getByRole("button", { name: /submit/i })'),
test('await page.getByTestId("my-test-button").click()'),
test(
'await page.getByRole("button").filter({ hasText: "Add to cart" }).click()'
),

test('await frame.getByRole("button")'),

test(
'const section = page.getByRole("section"); section.getByRole("button")'
),

// bare calls
test('() => page.locator'),
],
});

0 comments on commit 7b9ec83

Please sign in to comment.