Repository: playwright-community/eslint-plugin-playwright
Pull request #256 (2024-02-27 06:30:14)

[noissue] Change from waitForUrl to waitForURL
Just fixed small typos of `waitForUrl` that does not exist.


Patch:
diff --git a/docs/rules/no-wait-for-selector.md b/docs/rules/no-wait-for-selector.md
index 5e78e01..90c6132 100644
--- a/docs/rules/no-wait-for-selector.md
+++ b/docs/rules/no-wait-for-selector.md
@@ -12,6 +12,6 @@ Examples of **correct** code for this rule:
 
 ```javascript
 await page.waitForLoadState();
-await page.waitForUrl('/home');
+await page.waitForURL('/home');
 await page.waitForFunction(() => window.innerWidth < 100);
 ```
diff --git a/docs/rules/no-wait-for-timeout.md b/docs/rules/no-wait-for-timeout.md
index e8b10d6..1d94295 100644
--- a/docs/rules/no-wait-for-timeout.md
+++ b/docs/rules/no-wait-for-timeout.md
@@ -14,7 +14,7 @@ Examples of **correct** code for this rule:
 // Use signals such as network events, selectors becoming visible and others instead.
 await page.waitForLoadState();
 
-await page.waitForUrl('/home');
+await page.waitForURL('/home');
 
 await page.waitForFunction(() => window.innerWidth < 100);
 ```
diff --git a/src/rules/require-hook.ts b/src/rules/require-hook.ts
index a8d79ba..1fed3a5 100644
--- a/src/rules/require-hook.ts
+++ b/src/rules/require-hook.ts
@@ -58,10 +58,7 @@ export default {
 
     return {
       CallExpression(node) {
-        if (
-          !isTypeOfFnCall(context, node, ['describe']) ||
-          node.arguments.length < 2
-        ) {
+        if (!isTypeOfFnCall(context, node, ['describe'])) {
           return;
         }
 
diff --git a/src/rules/valid-describe-callback.ts b/src/rules/valid-describe-callback.ts
index 515546e..25f4c40 100644
--- a/src/rules/valid-describe-callback.ts
+++ b/src/rules/valid-describe-callback.ts
@@ -20,7 +20,7 @@ export default {
     return {
       CallExpression(node) {
         const call = parseFnCall(context, node);
-        if (call?.type !== 'describe') return;
+        if (call?.group !== 'describe') return;
 
         if (node.arguments.length < 1) {
           return context.report({
diff --git a/src/rules/valid-title.ts b/src/rules/valid-title.ts
index 6d575c1..d5c8031 100644
--- a/src/rules/valid-title.ts
+++ b/src/rules/valid-title.ts
@@ -111,9 +111,7 @@ export default {
         }
 
         const [argument] = node.arguments;
-        if (!argument) {
-          return;
-        }
+        if (!argument) return;
 
         if (!isStringNode(argument)) {
           if (
diff --git a/src/utils/parseFnCall.ts b/src/utils/parseFnCall.ts
index 01fd077..1294b9c 100644
--- a/src/utils/parseFnCall.ts
+++ b/src/utils/parseFnCall.ts
@@ -4,6 +4,7 @@ import {
   findParent,
   getParent,
   getStringValue,
+  isFunction,
   isIdentifier,
   isStringNode,
   StringNode,
@@ -143,7 +144,7 @@ const resolveToPlaywrightFn = (
   };
 };
 
-export type FnType =
+export type FnGroup =
   | 'describe'
   | 'expect'
   | 'hook'
@@ -151,7 +152,9 @@ export type FnType =
   | 'test'
   | 'unknown';
 
-function determinePlaywrightFnType(name: string): FnType {
+export type FnType = FnGroup | 'config';
+
+function determinePlaywrightFnGroup(name: string): FnGroup {
   if (name === 'step') return 'step';
   if (name === 'expect') return 'expect';
   if (name === 'describe') return 'describe';
@@ -210,7 +213,7 @@ const findModifiersAndMatcher = (
 };
 
 function getExpectArguments(
-  call: Omit<ParsedFnCall, 'type'>,
+  call: Omit<ParsedFnCall, 'group' | 'type'>,
 ): ESTree.CallExpression['arguments'] {
   return findParent(call.head.node, 'CallExpression')?.arguments ?? [];
 }
@@ -239,6 +242,7 @@ interface BaseParsedFnCall {
 }
 
 interface ParsedGeneralFnCall extends BaseParsedFnCall {
+  group: Exclude<FnGroup, 'expect'>;
   type: Exclude<FnType, 'expect'>;
 }
 
@@ -253,13 +257,14 @@ export interface ParsedExpectFnCall
   extends BaseParsedFnCall,
     ModifiersAndMatcher {
   args: ESTree.CallExpression['arguments'];
+  group: 'expect';
   type: 'expect';
 }
 
 export type ParsedFnCall = ParsedGeneralFnCall | ParsedExpectFnCall;
 
 const parseExpectCall = (
-  call: Omit<ParsedFnCall, 'type'>,
+  call: Omit<ParsedFnCall, 'group' | 'type'>,
 ): ParsedExpectFnCall | string => {
   const modifiersAndMatcher = findModifiersAndMatcher(call.members);
 
@@ -270,6 +275,7 @@ const parseExpectCall = (
   return {
     ...call,
     args: getExpectArguments(call),
+    group: 'expect',
     type: 'expect',
     ...modifiersAndMatcher,
   };
@@ -306,7 +312,10 @@ export const findTopMostCallExpression = (
   return top as ESTree.CallExpression & Rule.NodeParentExtension;
 };
 
-function parse(context: Rule.RuleContext, node: ESTree.CallExpression) {
+function parse(
+  context: Rule.RuleContext,
+  node: ESTree.CallExpression,
+): ParsedFnCall | string | null {
   const chain = getNodeChain(node);
 
   if (!chain?.length) {
@@ -328,14 +337,14 @@ function parse(context: Rule.RuleContext, node: ESTree.CallExpression) {
   // etc. we need to test the second link in the chain to find the true type.
   if (name === 'test' && links.length > 1) {
     const nextLinkName = links[1];
-    const nextLinkType = determinePlaywrightFnType(nextLinkName);
+    const nextLinkGroup = determinePlaywrightFnGroup(nextLinkName);
 
-    if (nextLinkType !== 'unknown') {
+    if (nextLinkGroup !== 'unknown') {
       name = nextLinkName;
     }
   }
 
-  const parsedFnCall: Omit<ParsedFnCall, 'type'> = {
+  const parsedFnCall: Omit<ParsedFnCall, 'group' | 'type'> = {
     head: { ...resolved, node: first },
     // every member node must have a member expression as their parent
     // in order to be part of the call chain we're parsing
@@ -343,9 +352,9 @@ function parse(context: Rule.RuleContext, node: ESTree.CallExpression) {
     name,
   };
 
-  const type = determinePlaywrightFnType(name);
+  const group = determinePlaywrightFnGroup(name);
 
-  if (type === 'expect') {
+  if (group === 'expect') {
     const result = parseExpectCall(parsedFnCall);
 
     // If the `expect` call chain is not valid, only report on the topmost node
@@ -386,7 +395,21 @@ function parse(context: Rule.RuleContext, node: ESTree.CallExpression) {
     return null;
   }
 
-  return { ...parsedFnCall, type };
+  // If the call is a configuration hook. E.g., `test.skip(true)`, `test.use()`,
+  // `test.describe.configure()`, etc.
+  let type: FnType = group;
+  if (
+    (name === 'test' || name === 'describe') &&
+    (node.arguments.length !== 2 || !isFunction(node.arguments[1]))
+  ) {
+    type = 'config';
+  }
+
+  return {
+    ...parsedFnCall,
+    group,
+    type,
+  };
 }
 
 const cache = new WeakMap<


Test patch:
diff --git a/src/rules/no-conditional-in-test.ts b/src/rules/no-conditional-in-test.ts
index b9c64ad..400160e 100644
--- a/src/rules/no-conditional-in-test.ts
+++ b/src/rules/no-conditional-in-test.ts
@@ -1,6 +1,6 @@
 import { Rule } from 'eslint';
-import { findParent, getStringValue } from '../utils/ast';
-import { parseFnCall } from '../utils/parseFnCall';
+import { findParent } from '../utils/ast';
+import { isTypeOfFnCall } from '../utils/parseFnCall';
 
 export default {
   create(context) {
@@ -8,19 +8,7 @@ export default {
       const call = findParent(node, 'CallExpression');
       if (!call) return;
 
-      const fnCall = parseFnCall(context, call);
-
-      // Allow conditional logic in `test.skip()` calls inside of tests
-      // https://playwright.dev/docs/api/class-test#test-skip-3
-      if (
-        fnCall?.type === 'test' &&
-        fnCall.members.some((member) => getStringValue(member) === 'skip') &&
-        call.arguments[0]?.type === 'LogicalExpression'
-      ) {
-        return;
-      }
-
-      if (fnCall?.type === 'test' || fnCall?.type === 'step') {
+      if (isTypeOfFnCall(context, call, ['test', 'step'])) {
         context.report({ messageId: 'conditionalInTest', node });
       }
     }
diff --git a/src/rules/no-networkidle.test.ts b/src/rules/no-networkidle.test.ts
index cf5f38b..737eda3 100644
--- a/src/rules/no-networkidle.test.ts
+++ b/src/rules/no-networkidle.test.ts
@@ -56,6 +56,6 @@ runRuleTester('no-networkidle', rule, {
 
     'this.page.waitForLoadState()',
     'page.waitForLoadState({ waitUntil: "load" })',
-    'page.waitForUrl(url, { waitUntil: "load" })',
+    'page.waitForURL(url, { waitUntil: "load" })',
   ],
 });
diff --git a/src/rules/no-skipped-test.ts b/src/rules/no-skipped-test.ts
index edd398b..44d5ed0 100644
--- a/src/rules/no-skipped-test.ts
+++ b/src/rules/no-skipped-test.ts
@@ -1,5 +1,5 @@
 import { Rule } from 'eslint';
-import { getStringValue, isFunction } from '../utils/ast';
+import { getStringValue } from '../utils/ast';
 import { parseFnCall } from '../utils/parseFnCall';
 
 export default {
@@ -10,7 +10,7 @@ export default {
         const allowConditional = !!options.allowConditional;
 
         const call = parseFnCall(context, node);
-        if (call?.type !== 'test' && call?.type !== 'describe') {
+        if (call?.group !== 'test' && call?.group !== 'describe') {
           return;
         }
 
@@ -19,12 +19,11 @@ export default {
 
         // If the call is a standalone `test.skip()` call, and not a test
         // annotation, we have to treat it a bit differently.
-        const isStandalone =
-          call.type === 'test' && !isFunction(node.arguments[1]);
+        const isStandalone = call.type === 'config';
 
         // If allowConditional is enabled and it's not a test/describe function,
         // we ignore any `test.skip` calls that have no arguments.
-        if (isStandalone && allowConditional && node.arguments.length) {
+        if (isStandalone && allowConditional) {
           return;
         }
 
diff --git a/src/rules/no-useless-await.test.ts b/src/rules/no-useless-await.test.ts
index d2f7373..770cc61 100644
--- a/src/rules/no-useless-await.test.ts
+++ b/src/rules/no-useless-await.test.ts
@@ -193,7 +193,7 @@ runRuleTester('no-useless-await', rule, {
     'page.locator(".my-element")',
 
     'await page.waitForLoadState({ waitUntil: "load" })',
-    'await page.waitForUrl(url, { waitUntil: "load" })',
+    'await page.waitForURL(url, { waitUntil: "load" })',
     'await page.locator(".hello-world").waitFor()',
   ],
 });
diff --git a/src/rules/require-top-level-describe.test.ts b/src/rules/require-top-level-describe.test.ts
index 2358ae6..d2afa06 100644
--- a/src/rules/require-top-level-describe.test.ts
+++ b/src/rules/require-top-level-describe.test.ts
@@ -177,6 +177,18 @@ runRuleTester('require-top-level-describe', rule, {
   valid: [
     'foo()',
     'test.info()',
+    'test.use({ locale: "en-US" })',
+    // 'test.only("that all is as it should be", () => {});',
+    // 'test.skip("that all is as it should be", () => {});',
+    // 'test.skip(({ browserName }) => browserName === "Chrome");',
+    // 'test.skip();',
+    // 'test.skip(true);',
+    // 'test.skip(browserName === "Chrome", "This feature is skipped on Chrome")',
+    // 'test.slow("that all is as it should be", () => {});',
+    // 'test.slow(({ browserName }) => browserName === "Chrome");',
+    // 'test.slow();',
+    // 'test.slow(browserName === "webkit", "This feature is slow on Mac")',
+    'test.describe.configure({ mode: "parallel" })',
     'test.describe("suite", () => { test("foo") });',
     'test.describe.only("suite", () => { test.beforeAll("my beforeAll") });',
     'test.describe.parallel("suite", () => { test.beforeEach("my beforeAll") });',
diff --git a/src/rules/valid-title.test.ts b/src/rules/valid-title.test.ts
index cbc0091..bfa266a 100644
--- a/src/rules/valid-title.test.ts
+++ b/src/rules/valid-title.test.ts
@@ -85,7 +85,22 @@ runRuleTester('valid-title', rule, {
   ],
   valid: [
     'test.describe("the correct way to properly handle all the things", () => {});',
+    'test.describe.configure({ mode: "parallel" })',
     'test("that all is as it should be", () => {});',
+    'test.use({ locale: "en-US" })',
+    'test.only("that all is as it should be", () => {});',
+    'test.skip("that all is as it should be", () => {});',
+    'test.skip(({ browserName }) => browserName === "Chrome");',
+    'test.skip();',
+    'test.skip(true);',
+    'test.slow(true, "Always");',
+    'test.skip(browserName === "Chrome", "This feature is skipped on Chrome")',
+    'test.slow("that all is as it should be", () => {});',
+    'test.slow(true);',
+    'test.slow(true, "Always");',
+    'test.slow(({ browserName }) => browserName === "Chrome");',
+    'test.slow();',
+    'test.slow(browserName === "webkit", "This feature is slow on Mac")',
     {
       code: 'test("correctly sets the value", () => {});',
       options: [
diff --git a/src/utils/parseFnCall.test.ts b/src/utils/parseFnCall.test.ts
index f86c72f..d3eed95 100644
--- a/src/utils/parseFnCall.test.ts
+++ b/src/utils/parseFnCall.test.ts
@@ -4,9 +4,9 @@ import * as ESTree from 'estree';
 import { getStringValue } from './ast';
 import {
   isSupportedAccessor,
-  ParsedFnCall,
+  type ParsedFnCall,
   parseFnCallWithReason,
-  ResolvedFnWithNode,
+  type ResolvedFnWithNode,
 } from './parseFnCall';
 import { runRuleTester, runTSRuleTester } from './rule-tester';
 
@@ -89,6 +89,7 @@ const sortKeys = (obj: unknown) =>
 const expectedParsedFnCallResultData = (result: TestParsedFnCall) => ({
   data: JSON.stringify({
     args: result.args,
+    group: result.group,
     head: sortKeys(result.head),
     matcher: result.matcher,
     matcherArgs: result.matcherArgs,
@@ -132,6 +133,7 @@ runRuleTester('expect', rule, {
           column: 1,
           data: expectedParsedFnCallResultData({
             args: ['x'],
+            group: 'expect',
             head: {
               local: 'expect',
               node: 'expect',
@@ -157,6 +159,7 @@ runRuleTester('expect', rule, {
           column: 1,
           data: expectedParsedFnCallResultData({
             args: ['x'],
+            group: 'expect',
             head: {
               local: 'expect',
               node: 'expect',
@@ -182,6 +185,7 @@ runRuleTester('expect', rule, {
           column: 1,
           data: expectedParsedFnCallResultData({
             args: [null],
+            group: 'expect',
             head: {
               local: 'expect',
               node: 'expect',
@@ -207,6 +211,7 @@ runRuleTester('expect', rule, {
           column: 1,
           data: expectedParsedFnCallResultData({
             args: [null],
+            group: 'expect',
             head: {
               local: 'expect',
               node: 'expect',
@@ -232,6 +237,7 @@ runRuleTester('expect', rule, {
           column: 1,
           data: expectedParsedFnCallResultData({
             args: [null],
+            group: 'expect',
             head: {
               local: 'expect',
               node: 'expect',
@@ -261,6 +267,7 @@ runRuleTester('expect', rule, {
           column: 1,
           data: expectedParsedFnCallResultData({
             args: [],
+            group: 'expect',
             head: {
               local: 'expect',
               node: 'expect',
@@ -290,6 +297,7 @@ runRuleTester('expect', rule, {
           column: 1,
           data: expectedParsedFnCallResultData({
             args: ['x'],
+            group: 'expect',
             head: {
               local: 'expect',
               node: 'expect',
@@ -319,6 +327,7 @@ runRuleTester('expect', rule, {
           column: 1,
           data: expectedParsedFnCallResultData({
             args: ['x'],
+            group: 'expect',
             head: {
               local: 'expect',
               node: 'expect',
@@ -344,6 +353,7 @@ runRuleTester('expect', rule, {
           column: 11,
           data: expectedParsedFnCallResultData({
             args: ['x'],
+            group: 'expect',
             head: {
               local: 'expect',
               node: 'expect',
@@ -380,6 +390,7 @@ runRuleTester('expect', rule, {
           column: 1,
           data: expectedParsedFnCallResultData({
             args: [],
+            group: 'expect',
             head: {
               local: 'expect',
               node: 'expect',
@@ -400,6 +411,7 @@ runRuleTester('expect', rule, {
           column: 1,
           data: expectedParsedFnCallResultData({
             args: [],
+            group: 'expect',
             head: {
               local: 'expect',
               node: 'expect',
@@ -420,6 +432,7 @@ runRuleTester('expect', rule, {
           column: 1,
           data: expectedParsedFnCallResultData({
             args: [],
+            group: 'expect',
             head: {
               local: 'expect',
               node: 'expect',
@@ -440,6 +453,7 @@ runRuleTester('expect', rule, {
           column: 1,
           data: expectedParsedFnCallResultData({
             args: [],
+            group: 'expect',
             head: {
               local: 'expect',
               node: 'expect',
@@ -506,6 +520,7 @@ runRuleTester('test', rule, {
         {
           column: 1,
           data: expectedParsedFnCallResultData({
+            group: 'test',
             head: {
               local: 'test',
               node: 'test',
@@ -526,6 +541,7 @@ runRuleTester('test', rule, {
         {
           column: 1,
           data: expectedParsedFnCallResultData({
+            group: 'step',
             head: {
               local: 'test',
               node: 'test',
@@ -540,12 +556,359 @@ runRuleTester('test', rule, {
         },
       ],
     },
+    {
+      code: 'test.only("a test", () => {});',
+      errors: [
+        {
+          column: 1,
+          data: expectedParsedFnCallResultData({
+            group: 'test',
+            head: {
+              local: 'test',
+              node: 'test',
+              original: null,
+            },
+            members: ['only'],
+            name: 'test',
+            type: 'test',
+          }),
+          line: 1,
+          messageId: 'details',
+        },
+      ],
+    },
+    {
+      code: 'test.skip("a test", () => {});',
+      errors: [
+        {
+          column: 1,
+          data: expectedParsedFnCallResultData({
+            group: 'test',
+            head: {
+              local: 'test',
+              node: 'test',
+              original: null,
+            },
+            members: ['skip'],
+            name: 'test',
+            type: 'test',
+          }),
+          line: 1,
+          messageId: 'details',
+        },
+      ],
+    },
+    {
+      code: 'test.slow("a test", () => {});',
+      errors: [
+        {
+          column: 1,
+          data: expectedParsedFnCallResultData({
+            group: 'test',
+            head: {
+              local: 'test',
+              node: 'test',
+              original: null,
+            },
+            members: ['slow'],
+            name: 'test',
+            type: 'test',
+          }),
+          line: 1,
+          messageId: 'details',
+        },
+      ],
+    },
+    {
+      code: 'test.use({ locale: "en-US" })',
+      errors: [
+        {
+          column: 1,
+          data: expectedParsedFnCallResultData({
+            group: 'test',
+            head: {
+              local: 'test',
+              node: 'test',
+              original: null,
+            },
+            members: ['use'],
+            name: 'test',
+            type: 'config',
+          }),
+          line: 1,
+          messageId: 'details',
+        },
+      ],
+    },
+    {
+      code: 'test.skip()',
+      errors: [
+        {
+          column: 1,
+          data: expectedParsedFnCallResultData({
+            group: 'test',
+            head: {
+              local: 'test',
+              node: 'test',
+              original: null,
+            },
+            members: ['skip'],
+            name: 'test',
+            type: 'config',
+          }),
+          line: 1,
+          messageId: 'details',
+        },
+      ],
+    },
+    {
+      code: 'test.skip(true)',
+      errors: [
+        {
+          column: 1,
+          data: expectedParsedFnCallResultData({
+            group: 'test',
+            head: {
+              local: 'test',
+              node: 'test',
+              original: null,
+            },
+            members: ['skip'],
+            name: 'test',
+            type: 'config',
+          }),
+          line: 1,
+          messageId: 'details',
+        },
+      ],
+    },
+    {
+      code: 'test.skip(({ browserName }) => browserName === "Chrome")',
+      errors: [
+        {
+          column: 1,
+          data: expectedParsedFnCallResultData({
+            group: 'test',
+            head: {
+              local: 'test',
+              node: 'test',
+              original: null,
+            },
+            members: ['skip'],
+            name: 'test',
+            type: 'config',
+          }),
+          line: 1,
+          messageId: 'details',
+        },
+      ],
+    },
+    {
+      code: 'test.skip(browserName === "Chrome", "This feature is skipped on Chrome")',
+      errors: [
+        {
+          column: 1,
+          data: expectedParsedFnCallResultData({
+            group: 'test',
+            head: {
+              local: 'test',
+              node: 'test',
+              original: null,
+            },
+            members: ['skip'],
+            name: 'test',
+            type: 'config',
+          }),
+          line: 1,
+          messageId: 'details',
+        },
+      ],
+    },
+    {
+      code: 'test.slow()',
+      errors: [
+        {
+          column: 1,
+          data: expectedParsedFnCallResultData({
+            group: 'test',
+            head: {
+              local: 'test',
+              node: 'test',
+              original: null,
+            },
+            members: ['slow'],
+            name: 'test',
+            type: 'config',
+          }),
+          line: 1,
+          messageId: 'details',
+        },
+      ],
+    },
+    {
+      code: 'test.slow(true)',
+      errors: [
+        {
+          column: 1,
+          data: expectedParsedFnCallResultData({
+            group: 'test',
+            head: {
+              local: 'test',
+              node: 'test',
+              original: null,
+            },
+            members: ['slow'],
+            name: 'test',
+            type: 'config',
+          }),
+          line: 1,
+          messageId: 'details',
+        },
+      ],
+    },
+    {
+      code: 'test.slow(({ browserName }) => browserName === "Chrome")',
+      errors: [
+        {
+          column: 1,
+          data: expectedParsedFnCallResultData({
+            group: 'test',
+            head: {
+              local: 'test',
+              node: 'test',
+              original: null,
+            },
+            members: ['slow'],
+            name: 'test',
+            type: 'config',
+          }),
+          line: 1,
+          messageId: 'details',
+        },
+      ],
+    },
+    {
+      code: 'test.slow(browserName === "Chrome", "This feature is skipped on Chrome")',
+      errors: [
+        {
+          column: 1,
+          data: expectedParsedFnCallResultData({
+            group: 'test',
+            head: {
+              local: 'test',
+              node: 'test',
+              original: null,
+            },
+            members: ['slow'],
+            name: 'test',
+            type: 'config',
+          }),
+          line: 1,
+          messageId: 'details',
+        },
+      ],
+    },
+  ],
+  valid: [
+    // Other functions
+    'it("is a  function", () => {});',
+    'ByDefault.sayHello();',
+  ],
+});
+
+runRuleTester('describe', rule, {
+  invalid: [
+    {
+      code: 'describe("when there is an error", () => {})',
+      errors: [
+        {
+          column: 1,
+          data: expectedParsedFnCallResultData({
+            group: 'describe',
+            head: {
+              local: 'describe',
+              node: 'describe',
+              original: null,
+            },
+            members: [],
+            name: 'describe',
+            type: 'describe',
+          }),
+          line: 1,
+          messageId: 'details',
+        },
+      ],
+    },
+    {
+      code: 'describe.skip("when there is an error", () => {})',
+      errors: [
+        {
+          column: 1,
+          data: expectedParsedFnCallResultData({
+            group: 'describe',
+            head: {
+              local: 'describe',
+              node: 'describe',
+              original: null,
+            },
+            members: ['skip'],
+            name: 'describe',
+            type: 'describe',
+          }),
+          line: 1,
+          messageId: 'details',
+        },
+      ],
+    },
+    {
+      code: 'test.describe("when there is an error", () => {})',
+      errors: [
+        {
+          column: 1,
+          data: expectedParsedFnCallResultData({
+            group: 'describe',
+            head: {
+              local: 'test',
+              node: 'test',
+              original: null,
+            },
+            members: ['describe'],
+            name: 'describe',
+            type: 'describe',
+          }),
+          line: 1,
+          messageId: 'details',
+        },
+      ],
+    },
+    {
+      code: 'test.describe.skip("when there is an error", () => {})',
+      errors: [
+        {
+          column: 1,
+          data: expectedParsedFnCallResultData({
+            group: 'describe',
+            head: {
+              local: 'test',
+              node: 'test',
+              original: null,
+            },
+            members: ['describe', 'skip'],
+            name: 'describe',
+            type: 'describe',
+          }),
+          line: 1,
+          messageId: 'details',
+        },
+      ],
+    },
     {
       code: 'context("when there is an error", () => {})',
       errors: [
         {
           column: 1,
           data: expectedParsedFnCallResultData({
+            group: 'describe',
             head: {
               local: 'context',
               node: 'context',
@@ -567,6 +930,7 @@ runRuleTester('test', rule, {
         {
           column: 1,
           data: expectedParsedFnCallResultData({
+            group: 'describe',
             head: {
               local: 'context',
               node: 'context',
@@ -591,6 +955,7 @@ runRuleTester('test', rule, {
         {
           column: 1,
           data: expectedParsedFnCallResultData({
+            group: 'describe',
             head: {
               local: 'context',
               node: 'context',
@@ -606,6 +971,7 @@ runRuleTester('test', rule, {
         {
           column: 1,
           data: expectedParsedFnCallResultData({
+            group: 'describe',
             head: {
               local: 'describe',
               node: 'describe',
@@ -621,10 +987,50 @@ runRuleTester('test', rule, {
       ],
       settings: { playwright: { globalAliases: { describe: ['context'] } } },
     },
+    {
+      code: 'describe.configure({ mode: "parallel" })',
+      errors: [
+        {
+          column: 1,
+          data: expectedParsedFnCallResultData({
+            group: 'describe',
+            head: {
+              local: 'describe',
+              node: 'describe',
+              original: null,
+            },
+            members: ['configure'],
+            name: 'describe',
+            type: 'config',
+          }),
+          line: 1,
+          messageId: 'details',
+        },
+      ],
+    },
+    {
+      code: 'test.describe.configure({ mode: "parallel" })',
+      errors: [
+        {
+          column: 1,
+          data: expectedParsedFnCallResultData({
+            group: 'describe',
+            head: {
+              local: 'test',
+              node: 'test',
+              original: null,
+            },
+            members: ['describe', 'configure'],
+            name: 'describe',
+            type: 'config',
+          }),
+          line: 1,
+          messageId: 'details',
+        },
+      ],
+    },
   ],
   valid: [
-    'it("is a  function", () => {});',
-    'ByDefault.sayHello();',
     {
       code: 'mycontext("skip this please", () => {});',
       settings: { playwright: { globalAliases: { describe: ['context'] } } },
@@ -645,6 +1051,7 @@ runTSRuleTester('typescript', rule, {
         {
           column: 1,
           data: expectedParsedFnCallResultData({
+            group: 'test',
             head: {
               local: 'test',
               node: 'test',