diff --git a/src/rules/expect-expect.test.ts b/src/rules/expect-expect.test.ts index 54c8249..725288d 100644 --- a/src/rules/expect-expect.test.ts +++ b/src/rules/expect-expect.test.ts @@ -83,6 +83,15 @@ runRuleTester('expect-expect', rule, { }); `, }, + { + code: javascript` + test.only('steps', async ({ page }) => { + await test.step.skip('first tab', async () => { + await expect(page.getByText('Hello')).toBeVisible(); + }); + }); + `, + }, { code: javascript` test('should fail', async ({ page }) => { diff --git a/src/rules/missing-playwright-await.ts b/src/rules/missing-playwright-await.ts index 0095812..ba6d4ce 100644 --- a/src/rules/missing-playwright-await.ts +++ b/src/rules/missing-playwright-await.ts @@ -31,6 +31,7 @@ const expectPlaywrightMatchers = [ ] const playwrightTestMatchers = [ + 'toBeAttached', 'toBeChecked', 'toBeDisabled', 'toBeEditable', @@ -38,23 +39,23 @@ const playwrightTestMatchers = [ 'toBeEnabled', 'toBeFocused', 'toBeHidden', + 'toBeInViewport', + 'toBeOK', 'toBeVisible', 'toContainText', + 'toHaveAccessibleErrorMessage', 'toHaveAttribute', + 'toHaveCSS', 'toHaveClass', 'toHaveCount', - 'toHaveCSS', 'toHaveId', 'toHaveJSProperty', - 'toBeOK', 'toHaveScreenshot', 'toHaveText', 'toHaveTitle', 'toHaveURL', 'toHaveValue', 'toHaveValues', - 'toBeAttached', - 'toBeInViewport', ] function getReportNode(node: ESTree.Node) { diff --git a/src/rules/no-conditional-in-test.test.ts b/src/rules/no-conditional-in-test.test.ts index d77fa9f..c0d232b 100644 --- a/src/rules/no-conditional-in-test.test.ts +++ b/src/rules/no-conditional-in-test.test.ts @@ -181,6 +181,16 @@ runRuleTester('no-conditional-in-test', rule, { `, errors: [{ column: 5, endColumn: 17, endLine: 3, line: 3, messageId }], }, + { + code: javascript` + test('test', async ({ page }) => { + await test.step.skip('step', async () => { + if (true) {} + }); + }); + `, + errors: [{ column: 5, endColumn: 17, endLine: 3, line: 3, messageId }], + }, { code: 'it("foo", () => { if (true) { expect(1).toBe(1); } });', errors: [{ column: 19, endColumn: 51, endLine: 1, line: 1, messageId }], diff --git a/src/rules/no-nested-step.test.ts b/src/rules/no-nested-step.test.ts index c571975..26e63d1 100644 --- a/src/rules/no-nested-step.test.ts +++ b/src/rules/no-nested-step.test.ts @@ -35,6 +35,24 @@ runRuleTester('max-nested-step', rule, { { column: 11, endColumn: 20, endLine: 6, line: 6, messageId }, ], }, + { + code: javascript` + test('foo', async () => { + await test.step("step1", async () => { + await test.step("nested step1", async () => { + await expect(true).toBe(true); + }); + await test.step.skip("nested step2", async () => { + await expect(true).toBe(true); + }); + }); + }); + `, + errors: [ + { column: 11, endColumn: 20, endLine: 3, line: 3, messageId }, + { column: 11, endColumn: 25, endLine: 6, line: 6, messageId }, + ], + }, // Global aliases { code: javascript` @@ -57,6 +75,7 @@ runRuleTester('max-nested-step', rule, { valid: [ 'await test.step("step1", () => {});', 'await test.step("step1", async () => {});', + 'await test.step.skip("step1", async () => {});', { code: javascript` test('foo', async () => { @@ -85,6 +104,18 @@ runRuleTester('max-nested-step', rule, { }); `, }, + { + code: javascript` + test('foo', async () => { + await test.step("step1", async () => { + await expect(true).toBe(true); + }); + await test.step.skip("step2", async () => { + await expect(true).toBe(true); + }); + }); + `, + }, // Global aliases { code: 'await it.step("step1", () => {});', diff --git a/src/rules/no-nested-step.ts b/src/rules/no-nested-step.ts index b764e0a..18c41e8 100644 --- a/src/rules/no-nested-step.ts +++ b/src/rules/no-nested-step.ts @@ -1,24 +1,16 @@ import { Rule } from 'eslint' -import ESTree from 'estree' -import { isPropertyAccessor } from '../utils/ast.js' import { createRule } from '../utils/createRule.js' - -function isStepCall(node: ESTree.Node): boolean { - const inner = node.type === 'CallExpression' ? node.callee : node - - if (inner.type !== 'MemberExpression') { - return false - } - - return isPropertyAccessor(inner, 'step') -} +import { isTypeOfFnCall } from '../utils/parseFnCall.js' export default createRule({ create(context) { const stack: number[] = [] function pushStepCallback(node: Rule.Node) { - if (node.parent.type !== 'CallExpression' || !isStepCall(node.parent)) { + if ( + node.parent.type !== 'CallExpression' || + !isTypeOfFnCall(context, node.parent, ['step']) + ) { return } @@ -35,7 +27,10 @@ export default createRule({ function popStepCallback(node: Rule.Node) { const { parent } = node - if (parent.type === 'CallExpression' && isStepCall(parent)) { + if ( + parent.type === 'CallExpression' && + isTypeOfFnCall(context, parent, ['step']) + ) { stack.pop() } } diff --git a/src/rules/no-skipped-test.test.ts b/src/rules/no-skipped-test.test.ts index 2846ac9..aaff974 100644 --- a/src/rules/no-skipped-test.test.ts +++ b/src/rules/no-skipped-test.test.ts @@ -247,6 +247,62 @@ runRuleTester('no-skipped-test', rule, { }, }, }, + { + code: 'test.step.skip("a step", async () => {});', + errors: [ + { + column: 11, + endColumn: 15, + line: 1, + messageId: 'noSkippedTest', + suggestions: [ + { + messageId, + output: 'test.step("a step", async () => {});', + }, + ], + }, + ], + }, + { + code: 'test.step.skip("a step", async () => {}, { timeout: 1000 });', + errors: [ + { + column: 11, + endColumn: 15, + line: 1, + messageId: 'noSkippedTest', + suggestions: [ + { + messageId, + output: 'test.step("a step", async () => {}, { timeout: 1000 });', + }, + ], + }, + ], + }, + { + code: 'it.step.skip("a step", async () => {});', + errors: [ + { + column: 9, + endColumn: 13, + line: 1, + messageId: 'noSkippedTest', + suggestions: [ + { + messageId, + output: 'it.step("a step", async () => {});', + }, + ], + }, + ], + settings: { + playwright: { + globalAliases: { test: ['it'] }, + }, + }, + }, ], valid: [ 'test("a test", () => {});', diff --git a/src/rules/no-skipped-test.ts b/src/rules/no-skipped-test.ts index ea88700..2e32ad9 100644 --- a/src/rules/no-skipped-test.ts +++ b/src/rules/no-skipped-test.ts @@ -10,7 +10,11 @@ export default createRule({ const allowConditional = !!options.allowConditional const call = parseFnCall(context, node) - if (call?.group !== 'test' && call?.group !== 'describe') { + if ( + call?.group !== 'test' && + call?.group !== 'describe' && + call?.group !== 'step' + ) { return } diff --git a/src/rules/valid-title.test.ts b/src/rules/valid-title.test.ts index 5224b23..52702b3 100644 --- a/src/rules/valid-title.test.ts +++ b/src/rules/valid-title.test.ts @@ -75,6 +75,18 @@ runRuleTester('valid-title', rule, { ], options: [{ disallowedWords: ['properly'] }], }, + { + code: 'test.step.skip(`that the value is set properly`, function () {})', + errors: [ + { + column: 16, + data: { word: 'properly' }, + line: 1, + messageId: 'disallowedWord', + }, + ], + options: [{ disallowedWords: ['properly'] }], + }, // Global aliases { code: 'it("the correct way to properly handle all things", () => {});', diff --git a/src/utils/parseFnCall.test.ts b/src/utils/parseFnCall.test.ts index e71fb8b..fee4e20 100644 --- a/src/utils/parseFnCall.test.ts +++ b/src/utils/parseFnCall.test.ts @@ -511,6 +511,48 @@ runRuleTester('test', rule, { }, ], }, + { + code: 'test.step.skip("a step", () => {});', + errors: [ + { + column: 1, + data: expectedParsedFnCallResultData({ + group: 'step', + head: { + local: 'test', + node: 'test', + original: null, + }, + members: ['step', 'skip'], + name: 'step', + type: 'step', + }), + line: 1, + messageId: 'details', + }, + ], + }, + { + code: 'test.step("a step", () => {}, { timeout: 1000 });', + errors: [ + { + column: 1, + data: expectedParsedFnCallResultData({ + group: 'step', + head: { + local: 'test', + node: 'test', + original: null, + }, + members: ['step'], + name: 'step', + type: 'step', + }), + line: 1, + messageId: 'details', + }, + ], + }, { code: 'test.only("a test", () => {});', errors: [ diff --git a/src/utils/parseFnCall.ts b/src/utils/parseFnCall.ts index 8539d5a..91f78b3 100644 --- a/src/utils/parseFnCall.ts +++ b/src/utils/parseFnCall.ts @@ -63,6 +63,7 @@ const VALID_CHAINS = new Set([ 'test.only', 'test.skip', 'test.step', + 'test.step.skip', 'test.slow', 'test.use', ])