Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feature] clipboard isolation #13097

Open
Meemaw opened this issue Mar 26, 2022 · 9 comments
Open

[feature] clipboard isolation #13097

Meemaw opened this issue Mar 26, 2022 · 9 comments

Comments

@Meemaw
Copy link

Meemaw commented Mar 26, 2022

Hey.

We're facing some issues with copy to clipboard functionality in Playwright:

page.evaluate(() => navigator.clipboard.readText())

In some cases this returns a completely random value, and not one that we would expect.

In our example we are getting an Ethereum address from Metamask by clicking on an button which copies it to clipboard. I'm attaching some screenshots from the trace viewer that demonstrate the problem:

Screenshot 2022-03-26 at 20 13 02

Screenshot 2022-03-26 at 20 12 07

Screenshot 2022-03-26 at 20 12 02

We can see on the last screenshot that the value is completely different than what we would expect by the screenshoot 1 and 2. Because it is still an Ethereum address, and because we run tests with high worker count (6), it could potentially be leaking from another test (we do this same thing in a lot of tests).

This does not happen just with MetaMask (but with some other wallets as well), which makes me doubt bug is on their side.

@yury-s
Copy link
Member

yury-s commented Mar 28, 2022

There is no clipboard isolation between different tests as of today. #8114 is the master bug for tracking clipboard support.

@rwoll
Copy link
Member

rwoll commented Mar 28, 2022

Per-above, closing to keep things consolidated to #8114.

@rwoll rwoll closed this as completed Mar 28, 2022
@aslushnikov
Copy link
Collaborator

Reopening this to track the clipboard isolation work.

@aslushnikov aslushnikov reopened this Jul 21, 2022
@aslushnikov aslushnikov changed the title [BUG] navigator.clipboard potentially leaking between tests? [feature] clipboard isolation Jul 21, 2022
@aslushnikov
Copy link
Collaborator

Example requests of clipboard isolation: #8114 (comment)

@s-h-a-d-o-w
Copy link
Contributor

s-h-a-d-o-w commented Aug 5, 2022

Assuming that we can trust browser APIs, this can be resolved by logging the write call:

async function spyClipboard(page: Page) {
  const log: string[] = []

  await page.exposeFunction('logValue', (value: string) => log.push(value))
  await page.addInitScript(() => {
    const originalImplementation = window.navigator.clipboard.writeText
    window.navigator.clipboard.writeText = async (...args) => {
      // @ts-expect-error Injected function
      logValue(args[0])
      await originalImplementation(...args)
    }
  })

  return log
}

// In your test
const clipboardCalls = await spyClipboard(page)
// ...
await expect(clipboardCalls[0]).toEqual('foo')

See also: https://playwright.dev/docs/mock#verifying-api-calls

Although I wonder... even though each test has their own context - is the window object still shared between contexts?

@luin
Copy link
Contributor

luin commented Feb 1, 2023

This could be super useful to enable parallelism for tests using clipboard API. In our case, only a subset of test cases use the clipboard API so we end up introducing a file lock as a workaround:

export const test = base.extend<{
  clipboard: [
    async ({ page, browserName }, use) => {
      const locker = new Locker('clipboard');
      await locker.lock();
      await use(
        new Clipboard(page),
      );
      await locker.release();
    },
    { timeout: 30000 },
  ],
});

@phungleson
Copy link

Hey @luin thanks for sharing, I ran into similar issue.

It would be great if you can share complete solution? as the current one seems missing some part of the code.

Thanks!

@luin
Copy link
Contributor

luin commented Feb 26, 2023

@phungleson I can't share the code as I wrote it for my company. But Locker is a tiny class where Locker#lock() basically tries to write and lock a local file with https://nodejs.org/api/fs.html#filehandlewritefiledata-options and will wait and retry if the file is already occupied by others.

@phungleson
Copy link

phungleson commented Feb 26, 2023

Ah cool thanks, yeah I managed to do something similar using cross-process-lock

// lockClipboard.ts
import { lock, UnlockFunction } from 'cross-process-lock';
import { writeFileSync, existsSync } from 'fs';

const name = 'tmp/clipboard';

// It should lock clipboard tests so that we can run one test at a time.
export const lockClipboard = async (): Promise<UnlockFunction> => {
  if (!existsSync(name)) {
    writeFileSync(name, '', 'utf8');
  }
  return await lock(name, { waitTimeout: 30000 });
};

Then in test.

let unlock: UnlockFunction;
test.beforeEach(async () => { unlock = await lockClipboard() });
test.afterEach(async () => await unlock());

It is not very elegant, hope clipboard isolation will come soon.

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

No branches or pull requests

7 participants