playwright icon indicating copy to clipboard operation
playwright copied to clipboard

[Feature] Support running extensions with Firefox

Open Pooort opened this issue 3 years ago • 65 comments

Context:

  • Playwright Version: 1.9.0
  • Operating System: Linux 5.4 Ubuntu 20.04.2 LTS (Focal Fossa)
  • Node.js version: 14.17.1
  • Browser: Firefox

Can't connect to the Firefox browser using this code: https://github.com/microsoft/playwright/issues/2644#issuecomment-647842059

The code freezes on the:

const browser = await firefox.connect({ wsEndpoint });

I see the browser with extension started, but Playwright can't connect to Firefox.

Pooort avatar Jun 24 '21 12:06 Pooort

Can't connect to the Firefox browser using this code: #2644 (comment)

@Pooort Well, this was a best-effort hack that didn't age well. So overall, that's not a bug: it was never officially supported.

We probably can support it properly, but that's an engineering effort. The more upvotes this issue gets, the higher we'll prioritize it!

aslushnikov avatar Jun 24 '21 21:06 aslushnikov

@aslushnikov I appreciate your effort. It would be nice to have extensions support. Voting up.

Badgerr avatar Jun 25 '21 07:06 Badgerr

agreed. this would be fantastic.

sdklein avatar Jun 25 '21 10:06 sdklein

This is definitely a much needed feature. Recently I was exploring to use Playwright and which going creating POC, I can to know through different github issues that Playwright always launch in incognito mode and there is not such easy way to launch a normal browser(https://github.com/microsoft/playwright/issues/2071). Incognito mode is not great with testing extensions.

It would be great if there is easy support for extension across all browsers since its one of the USP of Playwright.

rai-gaurav avatar Jun 26 '21 16:06 rai-gaurav

It would be nice to have extensions support. Voting up.

selang avatar Sep 27 '21 01:09 selang

Hi, It will be great to have firefox extension support. Voting up.

@aslushnikov are there any plans to make it works?

A-gambit avatar Mar 08 '22 22:03 A-gambit

I would also like to see this feature integrated.

NuLL3rr0r avatar Jun 25 '22 10:06 NuLL3rr0r

Being able to test a website with multiple browsers and commonly used extensions all with the same code will be a real game changer.

AxxiD avatar Jul 10 '22 19:07 AxxiD

I found another workaround to install add-ons to Firefox. Firefox allows installing addons via RDP (Remote Debugging Protocol). The following shows how to install addons by the RDP client implemented in web-ext.

import { firefox } from 'playwright';
// 'remote' is not defined by "exports" in package.json
import { connect } from './node_modules/web-ext/lib/firefox/remote.js';

const RDP_PORT = 12345;

(async () => {
  const browser = await firefox.launch({
    headless: false,
    args: [ '-start-debugger-server', String(RDP_PORT) ],
    firefoxUserPrefs: {
      'devtools.debugger.remote-enabled': true,
      'devtools.debugger.prompt-connection': false,
    }
  });

  const client = await connect(RDP_PORT);
  const resp = await client.installTemporaryAddon("path/to/addon/directory");
  console.log("Installed addon with ID", resp.addon.id);

  const page = await browser.newPage();
  await page.goto('https://mozilla.org');

  // ...
})();

Although this example imports an internal module, I believe it's better to use another client, such as Foxdriver (or implement a client yourself).

ueokande avatar Aug 01 '22 13:08 ueokande

@ueokande Here's a full native Node.js implementation of this - no need to use web-ext or any external client:

import { Buffer } from 'buffer';
import net from 'net';

export const loadFirefoxAddon = (port: number, host: string, addonPath: string) => {
    return new Promise<boolean>((resolve) => {
        const socket = net.connect({
            port,
            host,
        });

        let success = false;

        socket.once('error', () => {});
        socket.once('close', () => {
            resolve(success);
        });

        const send = (data: Record<string, string>) => {
            const raw = Buffer.from(JSON.stringify(data));

            socket.write(`${raw.length}`);
            socket.write(':');
            socket.write(raw);
        };

        send({
            to: 'root',
            type: 'getRoot',
        });

        const onMessage = (message: any) => {
            if (message.addonsActor) {
                send({
                    to: message.addonsActor,
                    type: 'installTemporaryAddon',
                    addonPath,
                });
            }

            if (message.addon) {
                success = true;
                socket.end();
            }

            if (message.error) {
                socket.end();
            }
        };

        const buffers: Buffer[] = [];
        let remainingBytes = 0;

        socket.on('data', (data) => {
            while (true) {
                if (remainingBytes === 0) {
                    const index = data.indexOf(':');

                    buffers.push(data);

                    if (index === -1) {
                        return;
                    }

                    const buffer = Buffer.concat(buffers);
                    const bufferIndex = buffer.indexOf(':');

                    buffers.length = 0;
                    remainingBytes = Number(buffer.subarray(0, bufferIndex).toString());

                    if (!Number.isFinite(remainingBytes)) {
                        throw new Error('Invalid state');
                    }

                    data = buffer.subarray(bufferIndex + 1);
                }

                if (data.length < remainingBytes) {
                    remainingBytes -= data.length;
                    buffers.push(data);
                    break;
                } else {
                    buffers.push(data.subarray(0, remainingBytes));

                    const buffer = Buffer.concat(buffers);
                    buffers.length = 0;

                    const json = JSON.parse(buffer.toString());
                    queueMicrotask(() => {
                        onMessage(json);
                    });

                    const remainder = data.subarray(remainingBytes);
                    remainingBytes = 0;

                    if (remainder.length === 0) {
                        break;
                    } else {
                        data = remainder;
                    }
                }
            }
        });
    });
};

szmarczak avatar Aug 11 '22 09:08 szmarczak

Hello All ,

I have tried the below code in java to launch extension , its working but its getting launged in nightly . Is this still a limitation or something wrong with the code . Can some one help me here pls ?

Playwright playwright = Playwright.create() Path extpath=Paths get("profile\extension)

List<String> Args= new ArrayList<>();

Args.add("--dusable-extension-except="+extpath); Args.add("--load-extension="+ extpath)

BrowserType.LaunchPersistentContextOptions lp= new BrowserType.LaunchPersistentContextOptions(); lp.setChannel("firefox") lp.setHeadless(false) lp.args=Args lp.setDevtools(false) Path path= Paths.get(path to profile folder) BrowserContext browser= playwright.firefox().launchPersistentContext(path, lp) Page page= browser.pages().get(0) page.navigate() ....

The above cose is working with extensionbut its loading in Nightly .. is it a limitation or something wrong with code ?

bibinmohana avatar Aug 15 '22 15:08 bibinmohana

add pls.

optionsx avatar Jan 15 '23 23:01 optionsx

add pls (python)

4n1qz5skwv avatar Apr 16 '23 16:04 4n1qz5skwv

Hi! I have published npm package which enables you to load an extensions on playwright. https://github.com/ueokande/playwright-webextext

ueokande avatar Apr 18 '23 05:04 ueokande

@ueokande I used your package to install the addon. The addon installed successfully but it is asking for permissions and I am not able to give permissions to it. is there any way to do this ?

Screenshot from 2023-05-13 12-29-07

aryangupta701 avatar May 13 '23 07:05 aryangupta701

Much needed! (java)

gilgulim avatar Jun 08 '23 07:06 gilgulim

@ueokande nice work getting extension to load 👍

to playwright team:

however, page actions (goto, click, etc...) on moz-extension:// pages will not work until firefox patches are updated

e.g.

https://github.com/microsoft/playwright/blob/1277ec99008898cba23c3511d024972e42409963/browser_patches/firefox/juggler/content/JugglerFrameChild.jsm#L41-L43


some things work

e.g.

await page.waitForLoadState('networkidle');

  pw:channel:command {
  pw:channel:command   id: 6,
  pw:channel:command   guid: 'frame@44f2c0a9f446a32cee5a435dd16eca6c',
  pw:channel:command   method: 'goto',
  pw:channel:command   params: {
  pw:channel:command     url: 'moz-extension://hardcoded-nordvpn-extension-id/index.html',
  pw:channel:command     waitUntil: 'networkidle'
  pw:channel:command   }
  pw:channel:command } +215ms
  pw:channel:event {
  pw:channel:event   guid: 'frame@cc52296d171fadaeb877b44318785291',
  pw:channel:event   method: 'loadstate',
  pw:channel:event   params: { add: 'networkidle' }
  pw:channel:event } +166ms
  pw:channel:event {
  pw:channel:event   guid: 'frame@44f2c0a9f446a32cee5a435dd16eca6c',
  pw:channel:event   method: 'loadstate',
  pw:channel:event   params: { add: 'networkidle' }
  pw:channel:event } +1s

there is also this body https://wiki.mozilla.org/WebDriver/RemoteProtocol but it seems to be pretty dead

olso-nordsec avatar Jul 28 '23 10:07 olso-nordsec

what should it be on path/to/addon/directory?

const resp = await client.installTemporaryAddon("path/to/addon/directory");

gabbelitoV2 avatar Aug 16 '23 12:08 gabbelitoV2

should you download the extension and put it in a regular folder and write the path to it?

gabbelitoV2 avatar Aug 16 '23 12:08 gabbelitoV2

Bump, please add Firefox support(Java)

coarsehorse avatar Aug 18 '23 11:08 coarsehorse

Hello all, team that I work in is also very interested in having Firefox extension support in Playwright. This feature would greatly enhance our testing capabilities and streamline our processes. We hope this feature gets prioritized. Thank you!

KonradKarimi avatar Aug 24 '23 13:08 KonradKarimi

really need this guys

abdeljalil09 avatar Sep 11 '23 18:09 abdeljalil09

+1

Euro-pol avatar Sep 16 '23 09:09 Euro-pol

My team and I really need this feature request. Thanks!

ictsuzukicerby avatar Sep 20 '23 21:09 ictsuzukicerby

add please

dybon2023 avatar Oct 12 '23 00:10 dybon2023

Add it, please! It's useful

sinner avatar Oct 12 '23 21:10 sinner

+1

ivanabrkic avatar Oct 23 '23 19:10 ivanabrkic

Bumping this up. Please bring this much needed feature.

siddharth2023 avatar Oct 24 '23 04:10 siddharth2023

guys plz implement this :)

archevaultdan avatar Oct 25 '23 14:10 archevaultdan

This would be great

alexmi256 avatar Oct 27 '23 14:10 alexmi256