Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions spec/lint-plugin.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,25 @@ import { tmpdir } from "node:os";
import { join } from "node:path";
import { promisify } from "node:util";
import { test, expect } from "@playwright/test";
import lintPlugin from "../src/lint/plugin.js";

const execFileAsync = promisify(execFile);
const preferLocatorWaitsRules = { "middlewright/prefer-locator-waits": "error" };
const preferPositiveWaitsRules = { "middlewright/prefer-positive-waits": "error" };
const requireTimeoutCommentRules = { "middlewright/require-timeout-comment": "error" };

test("formats multiline lint guidance without outer indentation", () => {
const messages = [
lintPlugin.rules["prefer-positive-waits"].meta.messages.detached,
lintPlugin.rules["require-timeout-comment"].meta.messages.unexplained,
];

for (const message of messages) {
expect(message).toBe(message.trim());
expect(message).not.toMatch(/(^|\n)[ \t]+\S/);
}
});

test("fixes visible locator assertions to locator waits", async () => {
await using fixture = await lintFixture(
`await expect(page.getByText("Ready")).toBeVisible();\n`,
Expand Down
30 changes: 24 additions & 6 deletions src/lint/plugin.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import dedent from "dedent";

const requiredPatternsSchema = [
{
type: "object",
Expand All @@ -21,8 +23,8 @@ const preferLocatorWaits = {
fixable: "code",
schema: [],
messages: {
visible: "Use locator.waitFor() instead of expect(locator).toBeVisible().",
text: "Use locator.filter({ hasText }).waitFor() instead of expect(locator).toContainText().",
visible: "Use locator.waitFor() instead of expect(locator).toBeVisible(). That way you'll benefit from spinnerWaiter's automatic loading UI detection.",
text: "Use locator.filter({ hasText }).waitFor() instead of expect(locator).toContainText(). That way you'll benefit from spinnerWaiter's automatic loading UI detection.",
},
},
create(context: any) {
Expand Down Expand Up @@ -72,8 +74,17 @@ const requireTimeoutComment = {
},
schema: requiredPatternsSchema,
messages: {
unexplained:
"Usually remove the timeout and add loading UI for spinnerWaiter. If a product or Middlewright limit prevents that, add a nearby // comment matching every required pattern: {{patterns}}. See https://github.com/iterate/middlewright#dont-fix-slow-tests-with-longer-timeouts",
unexplained: dedent`
Avoid locator timeouts by using spinnerWaiter. Best ways to resolve:
- If there's already loading UI, just remove the timeout and rely on spinnerWaiter to wait for the loading UI.
- If there's no loading UI, remove the timeout and add loading UI for spinnerWaiter.
- If there's a loading UI but it takes even longer than the spinnerWaiter spinner timeout, use \`await spinnerWaiter.settings.run({ spinnerTimeout: 123_456 }, async () => ...)\` or similar to wait even longer for the spinner to complete.
- If there's loading UI from a part of the code that we don't control (e.g. a library) and it isn't matched by the default spinner selectors, use \`await spinnerWaiter.settings.run({ spinnerSelectors: ["myCustomSpinnerClass"] }, async () => ...)\`.
- If it is truly impossible for there to be loading UI, add a nearby // comment matching every required pattern: {{patterns}}.
- If you're in a block which has done \`await spinnerWaiter.settings.run({ disabled: true }, async () => ...)\`, you should probably *un-disable* for that block and apply the above suggestions to the inner code.

See https://github.com/iterate/middlewright#dont-fix-slow-tests-with-longer-timeouts for more details.
`,
},
},
create(context: any) {
Expand Down Expand Up @@ -120,8 +131,15 @@ const preferPositiveWaits = {
},
schema: requiredPatternsSchema,
messages: {
detached:
"Wait for positive UI instead of element detachment, or explain an exceptional detached wait in a nearby // comment matching every required pattern: {{patterns}}. See https://github.com/iterate/middlewright#prefer-positive-waits-over-absence",
detached: dedent`
Wait for positive UI instead of element detachment.
If there's no positive UI to wait for your first port of call should be to *add* the positive UI to the product.
If it is truly impossible for there to be a positive UI, then that is quite surprising and you should question that.
If you have questioned it and it is still impossible, then you should make sure you've first validated that the UI that's becoming detached previously was visible.
Then explain why it's impossible, and why it's safe to wait for detachment in a nearby // comment matching every required pattern: {{patterns}}.

See https://github.com/iterate/middlewright#prefer-positive-waits-over-absence for more details.
`,
},
},
create(context: any) {
Expand Down
Loading