Skip to content

Commit

Permalink
feat: Add allowed option for no-raw-locators (#189)
Browse files Browse the repository at this point in the history
* Docs and tests

* Update rule
  • Loading branch information
mskelton authored Jan 1, 2024
1 parent 5bc9a33 commit ae52f21
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 1 deletion.
34 changes: 34 additions & 0 deletions docs/rules/no-raw-locators.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,37 @@ await page.getByRole('button', {
name: 'Submit',
});
```

## Options

```json
{
"playwright/no-raw-locators": [
"error",
{
"allowed": ["iframe", "[aria-busy='false']"]
}
]
}
```

### `allowed`

An array of raw locators that are allowed. This helps for locators such as
`iframe` which does not have a ARIA role that you can select using `getByRole`.

By default, no raw locators are allowed (the equivalent of `{ "ignore": [] }`).

Example of **incorrect** code for the `{ "allowed": ["[aria-busy=false]"] }`
option:

```javascript
page.getByRole('navigation').and(page.locator('iframe'));
```

Example of **correct** code for the `{ "allowed": ["[aria-busy=false]"] }`
option:

```javascript
page.getByRole('navigation').and(page.locator('[aria-busy="false"]'));
```
31 changes: 30 additions & 1 deletion src/rules/no-raw-locators.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,31 @@
import { Rule } from 'eslint';
import { getStringValue, isPageMethod } from '../utils/ast';

/** Normalize data attribute locators */
function normalize(str: string) {
const match = /\[([^=]+?)=['"]?([^'"]+?)['"]?\]/.exec(str);
return match ? `[${match[1]}=${match[2]}]` : str;
}

export default {
create(context) {
const options = {
allowed: [] as string[],
...((context.options?.[0] as Record<string, unknown>) ?? {}),
};

function isAllowed(arg: string) {
return options.allowed.some((a) => normalize(a) === normalize(arg));
}

return {
CallExpression(node) {
if (node.callee.type !== 'MemberExpression') return;
const method = getStringValue(node.callee.property);
const arg = getStringValue(node.arguments[0]);
const isLocator = isPageMethod(node, 'locator') || method === 'locator';

if (isPageMethod(node, 'locator') || method === 'locator') {
if (isLocator && !isAllowed(arg)) {
context.report({ messageId: 'noRawLocator', node });
}
},
Expand All @@ -25,6 +42,18 @@ export default {
noRawLocator:
'Usage of raw locator detected. Use methods like .getByRole() or .getByText() instead of raw locators.',
},
schema: [
{
additionalProperties: false,
properties: {
allowed: {
items: { type: 'string' },
type: 'array',
},
},
type: 'object',
},
],
type: 'suggestion',
},
} as Rule.RuleModule;
64 changes: 64 additions & 0 deletions test/spec/no-raw-locators.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,28 @@ runRuleTester('no-raw-locators', rule, {
),
errors: [{ column: 77, endColumn: 100, line: 1, messageId }],
},

// Allowed
{
code: test('await page.locator("[aria-busy=false]")'),
errors: [{ column: 34, endColumn: 67, line: 1, messageId }],
options: [{ allowed: ['iframe'] }],
},
{
code: test(`await page.locator("[aria-busy=false]")`),
errors: [{ column: 34, endColumn: 67, line: 1, messageId }],
options: [{ allowed: ['iframe'] }],
},
{
code: test(`await page.locator("[aria-busy=false]")`),
errors: [{ column: 34, endColumn: 67, line: 1, messageId }],
options: [{ allowed: [`[aria-busy=true]`] }],
},
{
code: test(`await page.locator("[aria-busy='false']")`),
errors: [{ column: 34, endColumn: 69, line: 1, messageId }],
options: [{ allowed: [`[aria-busy=true]`] }],
},
],
valid: [
test('await page.click()'),
Expand All @@ -61,5 +83,47 @@ runRuleTester('no-raw-locators', rule, {

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

// Allowed
{
code: test('await page.locator("iframe")'),
options: [{ allowed: ['iframe'] }],
},
{
code: test(`await page.locator("[aria-busy=false]")`),
options: [{ allowed: [`[aria-busy=false]`] }],
},
{
code: test(`await page.locator("[aria-busy='false']")`),
options: [{ allowed: [`[aria-busy=false]`] }],
},
{
code: test(`await page.locator('[aria-busy="false"]')`),
options: [{ allowed: [`[aria-busy=false]`] }],
},
{
code: test(`await page.locator("[aria-busy=false]")`),
options: [{ allowed: [`[aria-busy='false']`] }],
},
{
code: test(`await page.locator('[aria-busy=false]')`),
options: [{ allowed: [`[aria-busy="false"]`] }],
},
{
code: test(`await page.locator("[aria-busy='false']")`),
options: [{ allowed: [`[aria-busy='false']`] }],
},
{
code: test(`await page.locator('[aria-busy="false"]')`),
options: [{ allowed: [`[aria-busy="false"]`] }],
},
{
code: test(`await page.locator("[aria-busy='false']")`),
options: [{ allowed: [`[aria-busy="false"]`] }],
},
{
code: test(`await page.locator('[aria-busy="false"]')`),
options: [{ allowed: [`[aria-busy='false']`] }],
},
],
});

0 comments on commit ae52f21

Please sign in to comment.