Skip to content

docs(test): document mouse-outside-viewport screenshot helper - #42446

Open
Karl Horky (karlhorky) wants to merge 5 commits into
microsoft:mainfrom
karlhorky:docs/screenshot-mouse-position
Open

docs(test): document mouse-outside-viewport screenshot helper#42446
Karl Horky (karlhorky) wants to merge 5 commits into
microsoft:mainfrom
karlhorky:docs/screenshot-mouse-position

Conversation

@karlhorky

@karlhorky Karl Horky (karlhorky) commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Mouse actions leave the pointer at their last position. If a navigation or re-render places another element under that position, expect(page).toHaveScreenshot() can capture an unrelated hover state.

Playwright keeps this browser behavior as-is in Firefox:

Andrey Lushnikov (@aslushnikov) in comment 1532168395: But a quick workaround would be to move mouse away before taking screenshot.

Chromium also preserves the last mouse position:

Dmitry Gozman (@dgozman) in comment 2493986807: We move it to (0;0) expecting that to be the most predictable position with the least number of side effects.

This adds concise guidance to the Visual comparisons page and a collapsed helper for repeated named screenshots. The helper moves the mouse to -1, -1 before calling toHaveScreenshot() and forwards its screenshot options.

The documentation notes that projects should choose another coordinate when an element at the viewport origin responds to hover.

  • Explain how preserved mouse position can affect screenshots
  • Add a collapsed helper for repeated named screenshots
  • Forward Playwright screenshot assertion options

Copilot AI lite review requested due to automatic review settings August 28, 2026 17:14
});
```

</details>

@karlhorky Karl Horky (karlhorky) Aug 28, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In case the helper function seems like too much, a simpler alternative without a helper function could be this:

await page.mouse.move(-1, -1);
await expect(page).toHaveScreenshot();

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates Playwright Test visual comparison documentation to explain how preserved mouse position can unintentionally introduce hover states into screenshot assertions, and provides a reusable helper pattern to mitigate it.

Changes:

  • Document why mouse position persistence can affect expect(page).toHaveScreenshot() results after navigations/rerenders.
  • Add a collapsible TypeScript helper that moves the mouse to (0, 0) before taking named screenshots.
  • Show an example of forwarding toHaveScreenshot() options through the helper.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread docs/src/test-snapshots-js.md Outdated
Comment thread docs/src/test-snapshots-js.md Outdated
screenshotName: string,
options?: PageAssertionsToHaveScreenshotOptions,
) {
await page.mouse.move(0, 0);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(0, 0) is still inside the viewport and commonly hovers a header or any other full width element

(-1, -1) reliably clears hover across all three browsers and matches Playwright’s internal reset behavior

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh interesting, I didn't find that position in the codebase - maybe I didn't look hard enough

Done in e158a8f

Comment thread docs/src/test-snapshots-js.md Outdated

export async function expectPageToHaveScreenshotWithMouseAtOrigin(
page: Page,
screenshotName: string,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the helper unnecessarily excludes the supported ReadonlyArray<string>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in b49e089

Karl Horky (karlhorky) and others added 4 commits August 29, 2026 08:17
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Karl Horky <karl.horky@gmail.com>
Signed-off-by: Karl Horky <karl.horky@gmail.com>
@karlhorky Karl Horky (karlhorky) changed the title docs(test): document mouse-at-origin screenshot helper docs(test): document mouse-outside-viewport screenshot helper Aug 29, 2026

@dgozman Dmitry Gozman (dgozman) left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we want this. There are so many tricks you can employ for better screenshot testing, not sure why this one stands out. The place for it is too prominent - I don't think anyone learning about visual comparisons has to think about this right away. We cannot guarantee that moving to (-1, -1) works consistently to one's expectations across browsers. I am also not sure that the suggested wrapper is the way to go - I personally would prefer explicit mouse.move() calls in the test, or make a custom expect assertion.

@karlhorky

Karl Horky (karlhorky) commented Aug 31, 2026

Copy link
Copy Markdown
Contributor Author

There are so many tricks you can employ for better screenshot testing, not sure why this one stands out

I think it's a pretty confusing behavior for users, and the official recommendation from the Playwright team has been to move the mouse (but these details are buried). This can make users and teams lose a lot of time. I think that makes it a good candidate for documentation.

Actually, I would personally lean towards Playwright changing the .toHaveScreenshot() method to permanently fix this for everyone, with an option such as resetMouse (default true). Playwright handles robustness in so many other ways, so it's confusing when something doesn't work like this (leaky state from previous pages). Thoughts on that alternative?

I personally would prefer explicit mouse.move() calls in the test

Yes, that's ok for 1-off calls, but this mouse reset behavior should be the default, so recommending users write 2 lines everywhere they use .toHaveScreenshot() is also not great.

or make a custom expect assertion

Yeah that was my other version - I ended up discarding because it was recreating too much of the .toHaveScreenshot() API:

import {
  expect as baseExpect,
  type Page,
  type PageAssertionsToHaveScreenshotOptions,
} from '@playwright/test';

export { test } from '@playwright/test';

export const expect = baseExpect.extend({
  async toHaveScreenshotWithMouseOutsideViewport(
    page: Page,
    screenshotName: string | ReadonlyArray<string>,
    options?: PageAssertionsToHaveScreenshotOptions,
  ) {
    await page.mouse.move(-1, -1);

    let pass: boolean;
    let matcherResult: any;
    let message = '';

    try {
      const expectation = this.isNot
        ? baseExpect(page).not
        : baseExpect(page);

      await expectation.toHaveScreenshot(screenshotName, options);
      pass = true;
    } catch (error: any) {
      matcherResult = error.matcherResult;
      message = error.message;
      pass = false;
    }

    if (this.isNot) {
      pass = !pass;
    }

    return {
      message: () => message,
      pass,
      name: 'toHaveScreenshotWithMouseOutsideViewport',
      expected: screenshotName,
      actual: matcherResult?.actual,
      log: matcherResult?.log,
      timeout: matcherResult?.timeout,
    };
  },
});

@dgozman

Copy link
Copy Markdown
Collaborator

I think the magnitude of the problem is overestimated. Over many years, there have been just a few people how ran into this.

I'd say things work very much as expected today. Whatever is hovered on the page will be captured by the screenshot - that's what the user sees. In fact, automatic mouse reset would just break people that screenshot on-hover popups or similar things. I understand that's not what you want, but that does not mean everyone wants that.

Let's start with filing an issue, and see how popular that would be. Given the past history, that seems unlikely though.

@karlhorky

Karl Horky (karlhorky) commented Aug 31, 2026

Copy link
Copy Markdown
Contributor Author

I think the magnitude of the problem is overestimated. Over many years, there have been just a few people how ran into this.

This is only based on public GitHub reports right? Not the people who didn't report it because they just accepted whatever snapshot Playwright gave them. I usually review any snapshots carefully, and I also missed an erroneous hover in a snapshot that I had - it was definitely wrong and it was caused by Playwright's stale mouse position behavior, but it was hard to notice. And if the "normal" styling effect would have changed for that element, the snapshot would not have caught it. So in these cases, the snapshot is worthless for that element (but not known to be worthless).

Whatever is hovered on the page will be captured by the screenshot - that's what the user sees.

That would be fine, for same-page behavior.

But the whole reason for this PR is subsequent navigations: the stale mouse positions persist across page navigations (state leaking across unexpected boundaries). The argument that "this is how a normal browser operates" is maybe idealistically correct, but this doesn't mean much to someone who is debugging why their element appears hovered in the snapshot after 3 subsequent page navigations. Also, Playwright already papers over a lot of the sharp edges of how a normal browser operates for the purpose of testing - I'm not sure why this would be an exception.

In fact, automatic mouse reset would just break people that screenshot on-hover popups or similar things. I understand that's not what you want, but that does not mean everyone wants that.

Maybe a middle ground that would resolve this issue and also the on-hover popup snapshots would be this issue instead:

Issue Title: Reset mouse position to Playwright internal reset location after navigations

Instead of changing the .toHaveScreenshot() API, move the change to Playwright's navigations APIs.

Happy to file either this one or the previous idea of adding a .toHaveScreenshot() option

In the meantime, what should we do with this PR? Would you accept a short 2-line code block with just the page.mouse.move() and expect(page).toHaveScreenshot()?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants