ruifigueira / playwright-crx

Playwright for chrome extensions
Apache License 2.0
81 stars 15 forks source link

No tabId provided for Runtime.enable, Network.enable, Runtime.runIfWaitingForDebugger #1

Closed rupakkarki27 closed 8 months ago

rupakkarki27 commented 8 months ago

Hey @ruifigueira, thanks for this implementation of playwright and it works very well in almost all scenarios, and I was able to bundle it in an extension version with vite and react. However, I am having some issues with certain sites for example proton mail. I'm trying to write a script that checks for the login email and password inputs, and fills it. However, the script just freezes after navigation to the page and then gives the errors as in the title. If I cancel the debugger then the sites loads normally, but at that point the automation won't work

However, it works when I try it with core playwright. I tried going through the playwright-crx to debug and possibly fix and contribute but I was not able to find anything, and could not determine where to start to contribute.

This is the small script that I used:

    await page.goto("https://www.proton.me", { waitUntil: "domcontentloaded" });
    await page.getByRole("link", { name: "Sign in" }).click();

    await page.waitForSelector("//input[@id='username']");

    await page.getByLabel("Email or username").click();
    await page.getByLabel("Email or username").fill(Config.proton.email);
    await page.getByLabel("Password").click();
    await page.getByLabel("Password").fill(Config.proton.password);
    await page.getByRole("button", { name: "Sign in" }).click();

and I have received the following stack trace when the script stops:

No tabId provided for Runtime.enable
_send @ index-345ed8d1.mjs?v=66092da6:123583
send @ index-345ed8d1.mjs?v=66092da6:123518
_rawSend @ index-345ed8d1.mjs?v=66092da6:107176
send @ index-345ed8d1.mjs?v=66092da6:107243
_sendMayFail @ index-345ed8d1.mjs?v=66092da6:107249
_onAttachedToTarget @ index-345ed8d1.mjs?v=66092da6:109806
(anonymous) @ index-345ed8d1.mjs?v=66092da6:109548
emit @ index-345ed8d1.mjs?v=66092da6:10034
(anonymous) @ index-345ed8d1.mjs?v=66092da6:107267
Promise.then (async)
_onMessage @ index-345ed8d1.mjs?v=66092da6:107264
_onMessage @ index-345ed8d1.mjs?v=66092da6:107185
_emitMessage @ index-345ed8d1.mjs?v=66092da6:123588
(anonymous) @ index-345ed8d1.mjs?v=66092da6:123468

It would be great if you would be able to point me at the right direction, and I'm hoping I could contribute to playwright-crx.

Thanks.

ruifigueira commented 8 months ago

Hi @rupakkarki27,

how did you get that page instance? chrome.debugger API requires a tabId, and playwright-crx should ensure that a page maps to a chrome tabId (that mapping can be found in crxTransport.ts.

The simplest way to instanciate a page with a backed tabId is to call crxApp.newPage:

import { crx } from 'playwright-crx';

// ...

const crxApp = await crx.start();
const page = await crxApp.newPage();
rupakkarki27 commented 8 months ago

Yes, that is what i did. I am sending background messages from the react app using chrome.runtime.sendMessage api, and receiving it in background.ts as such:

browser.runtime.onMessage.addListener(async (message: Message) => {
  const tab = await chrome.tabs.query({ currentWindow: true, active: true });

  const crxApp = await crx.start({ slowMo: 500 });

  const page = await crxApp.attach(tab[0].id!).catch(() => crxApp.newPage());

  // send the page instance to the script

}
ruifigueira commented 8 months ago

I suspect that tab[0].id is undefined. Try to output it with console.log, it will write into your extension service worker devtools.

You can open your extension devtools by inspecting the service worker view:

image

rupakkarki27 commented 8 months ago

The tab[0].id is not undefined. The issue is that the scripts navigates and goes to the page, but it's stuck on this loading screen. When I cancel, the page will load fine. This does not happen on all sites, proton mail is the only one I'm seeing this issue in.

image

Output of the tabs query:

{
    "tab": [
        {
            "active": true,
            "audible": false,
            "autoDiscardable": true,
            "discarded": false,
            "favIconUrl": "",
            "groupId": -1,
            "height": 823,
            "highlighted": true,
            "id": 512783461,
            "incognito": false,
            "index": 2,
            "mutedInfo": {
                "muted": false
            },
            "pinned": false,
            "selected": true,
            "status": "complete",
            "title": "Extensions",
            "url": "chrome://extensions/",
            "width": 1102,
            "windowId": 512783460
        }
    ]
}
ruifigueira commented 8 months ago

I was able to replicate the issue I also get that "stuck" screen, and with the exact errors you have. I'll have to investigate it a little bit, it seems to be a bug.

ruifigueira commented 8 months ago

stacktrace with sourcemaps:

No tabId provided for Runtime.enable
_send @ crxTransport.ts:163
send @ crxTransport.ts:89
_rawSend @ crConnection.ts:66
send @ crConnection.ts:158
_sendMayFail @ crConnection.ts:165
_onAttachedToTarget @ crPage.ts:748
(anonymous) @ crPage.ts:445
emit @ events.js:153
(anonymous) @ crConnection.ts:183
Promise.then (async)
_onMessage @ crConnection.ts:180
_onMessage @ crConnection.ts:76
_emitMessage @ crxTransport.ts:209
(anonymous) @ crxTransport.ts:200

Checking crPage.ts, target must be a Worker at that point.

I'm trying to create a simple test to check workers.

ruifigueira commented 8 months ago

The No tabId provided for (...) errors are related with service workers, not normal workers.

Workers are assigned to a page, and therefore it's possible to get the corresponding tab ID, but service workers are not. That's why browserContext object has serviceWorkers and page object only has workers, and workers() function documentation explicitly says that:

This does not contain ServiceWorkers

That being said, I'm not sure if the behaviour on the page is because of that.

But there's a workaround: as soon as chrome.debugger is detached from the page, it proceeds as expected, so you can detach and attach until the form is available:

const { id: tabId } = await chrome.tabs.create({ url: "https://account.proton.me/login" });
let page: Page;

while (true) {
  page = await crxApp.attach(tabId!);
  if (await page.locator("//input[@id='username']").isVisible())
    break;
  await crxApp.detach(tabId!);
  await new Promise(r => setTimeout(r, 1000));
}

await page.getByLabel("Email or username").click();
await page.getByLabel("Email or username").fill('foo');
await page.getByLabel("Password").click();
await page.getByLabel("Password").fill('bar');
rupakkarki27 commented 8 months ago

@ruifigueira, thank you for the workaround. It works pretty well but takes a bit longer. I'm working on any ways to optimize it further if I have time, but overall works great. Thanks.

ruifigueira commented 8 months ago

Finally I found out the reason for this problem: playwright forces that all targets (pages, workers, service workers) to wait for debugger when they auto attach:

      this._client.send('Target.setAutoAttach', { autoAttach: true, waitForDebuggerOnStart: true, flatten: true })

Playwright then sends a Runtime.runIfWaitingForDebugger to let it continue. As explained above, chrome.debugger can only send CDP instructions to tabs, so it doesn't seem to be possible to send the instruction for the service worker, and that's why it was stucked.

I fixed this by ensuring that service workers are never auto-attached.

ruifigueira commented 8 months ago

@rupakkarki27 I just released v0.1.0.

It is already available in npmjs.

Let me know if you give it a try to check if your problem is fixed.