microsoft / playwright

Playwright is a framework for Web Testing and Automation. It allows testing Chromium, Firefox and WebKit with a single API.
https://playwright.dev
Apache License 2.0
66.22k stars 3.62k forks source link

[Question] Attaching playwright to an existing browser window? #1985

Closed benjamingr closed 4 years ago

benjamingr commented 4 years ago

Hey,

First of all thank you for this wonderful library <3

I'm working on a feature for playwright recorder to let you more easily run playwright tests from the browser.

I want to attach playwright to a context that is already running (opened with ChromeDriver if that matters):

    const browser = await playwright.chromium.connect({
        wsEndpoint: cdpUrl,
    });
    const contexts = browser.contexts(); // empty

    // how do I get the existing already open pages?

    const context = await browser.newContext({viewport: null })
    const pages = await context.pages(); // empty

I checked puppeteer and it was easy to get working like this:

    const browser = await puppeteer.connect({
        browserWSEndpoint: cdpUrl,
        defaultViewport: null, // disable puppeteer resize
        product: 'chrome', 
    });
    const pages = await browser.pages();

    const page = last(pages);

Is there an official method to obtain existing browser contexts?

pavelfeldman commented 4 years ago

No official method. And I am not sure we would want to support something like this. Curious what you are doing though!

benjamingr commented 4 years ago

I have a browser I launched with ChromeDriver (the Testim editor) and I want it to run a test authored in playwright in the same browser.

In puppeteer this is pretty easy but in playwright I don't have a way to ask for an existing context.

What if I hit the json endpoint at /json and not /json/version and get the debugger URL for the page? Is there a way to get playwright to talk to that webaocket URL?

benjamingr commented 4 years ago

I'll try to describe the use case:

The feature I'm asking for enables mixing and matching playwright with selenium or puppeteer more easily. I specifically need it to run the playwright playground (mentioned above) on tabs controlled inside ChromeDriver

hoIIer commented 4 years ago

hey I'll add to this as a new playwright user, I'm wondering if it's possible to run the tests in a real browser and e.g. throw a debugger to pause the test and inspect it etc? With selenium I know this is possible, and in other testing frameworks while running render tests it's possible to pause and view the browser window etc... Just curious as right now I'm building tests and running them but it's effectively in the dark, I don't know how to pause and inspect what the test is doing (also might just be that I'm uninformed on this subject!).

benjamingr commented 4 years ago

I don't know how to pause and inspect what the test is doing (also might just be that I'm uninformed on this subject!).

When you launch a browser with playwright - you can attach to it using the open debugging port. If you're using Chrome you can open a new tab and navigate to chrome://inspect or if you're using VSCode you can get the the vscode chrome debug extension which can attach to a running browser controlled by playwright.

This ticket is about playwright itself attaching to a session created with another tool - for example imagine being able to write new code for your selenium tests with playwright and mix the two :]

hoIIer commented 4 years ago

@benjamingr ah ok gotcha! I figured it was just my own lack of knowledge. I'd be interested to learn more about how to do this when developing using e.g. mocha. Mixing playwright and selenium sounds interesting, I could see how it'd be useful if you have an existing large selenium codebase and don't want to port it over entirely... cheers!

tymfear commented 4 years ago

Probably I got issue related to this one. I have Selenoid cluster deployed in cloud and would like to use it as infrastructure for PW tests. So I create chrome instance on the cluster with plain HTTP call an trying to connect to that session like:

 const sessionId = await createSession();
 const browser = await chromium.connect({wsEndpoint: `ws://localhost:4444/devtools/${sessionId}`});
const page = await browser.newPage();

And as a result I'm getting an error

  Error {
    message: 'Protocol error (Browser.setDownloadBehavior): \'Browser.setDownloadBehavior\' wasn\'t found',
  }

So is that related to the fact that there is no way to connect to existing session or I just doing something wrong?

benjamingr commented 4 years ago

So is that related to the fact that there is no way to connect to existing session or I just doing something wrong?

You are not doing something wrong. This is the same issue. I ran into the same issue when evaluating selenoid.

Your only option is to not control the grid with a tool like selenoid and inverse who creates the session. That is: create the session with playwright, create chromedriver on the same port and have it attach to the session.

Note that this is not a technical limitation. It's a choice the developers are making to not support this use case (for now).

I would write code to bypass it (it's possible to "hack around it" as it's not a protocol limitation) but I'm trying to see if:

mxschmitt commented 4 years ago

The issue is that for WebKit and Firefox custom patched versions are used so for them it would be quite hard. Chromium related changes are not custom patched, but they are made directly upstream in Chromium, so in the end if you are using Chromium Canary (latest build) it should work.

Have you tried it with the latest Chromium Canary?

(To connect to existing browsers, launchServer can be used, but thats probably not what you want since you have ChromeDriver based instances.)

benjamingr commented 4 years ago

@mxschmitt browserType.launchServer looks cool regardless - is there a tutorial about it?

You are correct that this is not a limitation of the Chrome version itself. Playwright happily creates new sessions after connecting to the browser. It's just not able to find existing contexts and pages.

This is not a protocol limitation but an API choice I'm hoping you can relax.

I am fine with this working only in Chromium initially - in particular even if this only works with Chromium with this revision that's fine :]

AutoSponge commented 4 years ago

I tried this today. The problem I'm seeing is that connecting to an existing wsEndpoint does not attach to the existing page targets.

I started playwright with a browserServer. Then I started another instance of playwright and connected to the existing wsEndpoint.

const context = await browser.newContext(contextConfig);
const pages = await context.pages();
pages.length // 0;

Yet, the code in CRBrowser appears to try attaching to existing pages. However, I think the logic only fires on Target.targetCreated event in response to the CDP method. There's no Target.getTargets then a loop over the existing targets to create PageDelegates.

tymfear commented 4 years ago

Well, @pavelfeldman could you provide some info on Playwright team's view on that feature. This is a big thing for projects with existing infrastructure and large codebase. At least that would help to understand if it worth expecting such feature at all, or should I (and others who has existing infra and wouldn't want to get rid of it) stop waiting for it? (I'm checking this ticket daily, but it's a bit annoying)

pavelfeldman commented 4 years ago

Sorry, I dropped the ball on this one. Let me try to answer the questions and explain the rationale. I'll go down the thread and try to address everything.

@benjamingr

Imagine I already have a large codebase written in Selenium. I would like to author new features with playwright instead or to leverage a library written in playwright.

Playwright relies upon owning and configuring the browser contexts to ensure that the emulation and other parameters survive site isolation and apply to popup windows. When you say your context is offline, we want to make sure that pages opened with window.open from yours are also offline. When you set up a route to block certain sites, we want that to apply to popups or when you navigate and switch underlying process. To achieve that, we throttle the renderer creation inside the browser to make sure that by the time it runs, it already has all the emulation bits set up. Selenium can't do that, it does not have these capabilities, so in your story Playwright needs to be the primary driver since it needs to control more environment.

Sadly, Selenium will jump in and since it also assumes it is the only driver, it will start messing with the Playwright's viewport emulation settings.

The feature I'm asking for enables mixing and matching playwright with selenium or puppeteer more easily. I specifically need it to run the playwright playground (mentioned above) on tabs controlled inside ChromeDriver

It sounds like you only want to show case the basic page.* methods in your playground and don't care about the emulation. Basically a lightweight version of the API. Unfortunately, there is no fine boundary like that and as soon as you do something with the file chooser, the drivers will fight.

pavelfeldman commented 4 years ago

@burritoIand

I'm wondering if it's possible to run the tests in a real browser and e.g. throw a debugger to pause the test and inspect it etc?

Launch the browser with the { headless: false } parameter, pause in your test, open the devtools to debug. Basically same story, you can open DevTools along with the test in Chromium and Firefox. Opening WebKit Web Inspector will terminate the test, but it'll let you poke around the page anyways - you would just need to restart the test.

pavelfeldman commented 4 years ago

@tymfear

I have Selenoid cluster deployed in cloud and would like to use it as infrastructure for PW tests.

Playwright is picky about the version of Chromium it runs against, it is only guaranteed to work against the one it downloads itself. You point at a Chromium instance that is too old and does not have a capability Playwright uses.

pavelfeldman commented 4 years ago

@AutoSponge

I tried this today. The problem I'm seeing is that connecting to an existing wsEndpoint does not attach to the existing page targets.

Yes, this is deliberate, we consider web socket clients to be isolated tenants. They don't see each other contexts, they basically use browser as a factory of these contexts for users connected over the wire.

pavelfeldman commented 4 years ago

@tymfear

At least that would help to understand if it worth expecting such feature at all, or should I (and others who has existing infra and wouldn't want to get rid of it) stop waiting for it?

Mixing the two drivers will not work with the present scope of Playwright capabilities. But there might be a reasonable variation / compromise that we could achieve. For that, we need to better understand the end result you are trying to achieve.

Another aspect of targeting existing Selenium clouds is that Playwright follows the "tests are a part of the CI" paradigm. We imply that you are using Travis / GitHub / Circle / in-house CI infrastructure that runs the tests for your commits. In this story, test is located next to the running browser and we can be pretty chatty in terms of the CDP messages. Doing what Selenium does and making each CDP request fly over the network would significantly contribute to the latency and flakiness. In order to mitigate that, we would need to (re)invent a higher-level protocol and seed our own agent in the Selenium cloud, at which point it stops being an "existing infrastructure".

(I'm checking this ticket daily, but it's a bit annoying)

😢sorry about that

pavelfeldman commented 4 years ago

All in all, eager to help reusing the existing infrastructures, but we need to basically start with understanding the constraints to make sure what we do makes sense.

benjamingr commented 4 years ago

Sadly, Selenium will jump in and since it also assumes it is the only driver, it will start messing with the Playwright's viewport emulation settings.

I can probably take care of that (either by PRs into ChromeDriver, Marionette or other tools) if that is the case. I think it's fair for automation tools to assume they are getting a "fresh context".

Selenium can't do that, it does not have these capabilities, so in your story Playwright needs to be the primary driver since it needs to control more environment.

The use cases I have are pretty simple and share a common theme:

It sounds like you only want to show case the basic page.* methods in your playground and don't care about the emulation. Basically a lightweight version of the API.

Yes exactly. I am fine if emulation doesn't work in that particular area, it would be better if it did (since I mean, both drivers are just CDP wrappers and execute the same Emulation.setDeviceMetricsOverride in all likelihood, even in Selenium we would just connect with chrome.debugger and execute the same command).

To be clear: all this works (pretty well) with Puppeteer, we mix and match puppeteer and selenium all the time and it's this (and admittedly - the change makes sense) shift in APIs that makes this impossible.

benjamingr commented 4 years ago

Since it's unrelated:

Another aspect of targeting existing Selenium clouds is that Playwright follows the "tests are a part of the CI" paradigm. We imply that you are using Travis / GitHub / Circle / in-house CI infrastructure that runs the tests for your commits. In this story, test is located next to the running browser and we can be pretty chatty in terms of the CDP messages. Doing what Selenium does and making each CDP request fly over the network would significantly contribute to the latency and flakiness. In order to mitigate that, we would need to (re)invent a higher-level protocol and seed our own agent in the Selenium cloud, at which point it stops being an "existing infrastructure".

This made me laugh out loud. I argued a bunch for this at Testim and I lost the argument. We have a mode where we use puppeteer-web and custom chrome.debugger transport to execute puppeteer code on the same browser from inside the browser. Alternatively we run our own automation using chrome.debugger (in certain modes) with close to zero latency (over the regular debugger and not remote debugger protocol).

It's not safe but it has zero latency and certainly lower than running playwright/puppeteer from a different machine.

I have found making each CDP request "fly over the network" to not that big of a deal in practice (eventhough for example a "click" Selenium commands is 3 CDP commands). I've found the overhead of network to only be a big deal if grids are far from your CI.

The current model (no grids) makes it very hard to scale playwright and puppeteer and I believe we're going to see many grid vendors provide playwright grids in the future.

(Also, there are grid vendors where the model is that you ship your Node code to the device - so there is very little latency.)

benjamingr commented 4 years ago

(and by the way - playwright will happily connect to the debugger in the browser and create a new context which selenium will happily find later on - so my current bad "workaround" is to connect playwright, open the tab with it and then use selenium to enter that tab)

benjamingr commented 4 years ago

An unrelated note: as an open source maintainer myself:

Sorry, I dropped the ball on this one. Let me try to answer the questions and explain the rationale. I'll go down the thread and try to address everything.

I really don't believe you owe me or anyone else here answers for this in a short manner. Especially not after answering the initial question within a day. I know some other comments sounded angry or disappointed but it's a tricky question and just because I asked for something or raised a challenging issue doesn't mean it should be a priority for you.

If this was posted in one of the repos I maintain:

(I'm checking this ticket daily, but it's a bit annoying)

I can only hope that I would have responded as charitably as you did (but probably not) :]

So thank you ❤️

tymfear commented 4 years ago

@pavelfeldman - do I understand it right, that if I find suitable chromedriver version (which was chrome v 81 I guess), I will be able to connect to existing browser? I don't use selenium at all, Selenoid cluster is handy for starting docker instances with chrome and providing the WS endpoint. So basically, what I want is the following scenario

Is that something that could be achieved by suitable chromedriver version?

Or, maybe, you have some examples of infra that will allow 100+ tests running in parallel? Maybe someone shared some examples?

benjamingr commented 4 years ago

@tymfear I can confirm that I have done that particular thing (selenoid with playwright) successfully. The only limitation is controlling existing tabs (and not creating a new context). Playwright will connect to the CDP url selenoid exposes.

You can just try it out, you still have to take care of session management but it's just wrapping things in a few more calls.

pipi1007 commented 4 years ago

I am having the same problem as @tymfear . I am getting below the exception when I launch a preinstalled Chrome (latest stable version) from playwright:

Exception has occurred: Error Error: Protocol error (Browser.setDownloadBehavior): 'Browser.setDownloadBehavior' wasn't found

I am using the executablePath in launchOptions. Is this supported by playwright, or I am doing something wrong?

pavelfeldman commented 4 years ago

@tymfear, @pipi1007 : Current stable (Chrome 81) is too old for Playwright 1.0. It requires at least Chrome 84 to pass the complete test suite. It will run against 83, but some of the features (downloads) won't be working. Chrome 83 goes stable next week, while Chrome 84 is planned for Jul 14, 2020.

pavelfeldman commented 4 years ago

@benjamingr

To be clear: all this works (pretty well) with Puppeteer, we mix and match puppeteer and selenium all the time and it's this (and admittedly - the change makes sense) shift in APIs that makes this impossible.

That makes sense. Puppeteer does not have a notion of the context-wide emulation, so it does not throttle targets. This bit is particularly risky since ChromeDriver will detect target as created while Playwright will be throttling it. It is likely to work for simple scenarios, but since we never targeted and/or tested this scenario, I'd be hesitant recommending it.

There is nothing impossible about it altogether, we just need to prioritize it and get you a 'Playwright lite' that can co-exist with Selenium.

This made me laugh out loud. I argued a bunch for this at Testim and I lost the argument.

Oh, too bad, tell them the guy that did CDP tells them that you were right. In all seriousness, I'd like to hold Playwright to a higher standard. If we engage into operation against the grid, we will eliminate chattiness. We are against flakes and latency after all. Operation against the grid also correlates with picking up other languages and that's where the higher level protocol becomes also handy.

The current model (no grids) makes it very hard to scale playwright and puppeteer and I believe we're going to see many grid vendors provide playwright grids in the future.

This one is particularly interesting. So far, we've been focusing on making Playwright a part of the local workflow and CI/CD. Our philosophy is that tests sit next to the runtime and there is no difference between your functional and e2e tests. You don't use the grid for your Jest / JSDom tests and we want e2e to feel just as easy and lightweight.

But at the same time, we don't want to be detached from the reality. So if we find this hybrid model that you are describing to be wide spread and actually enabling WebDriver community to improve the quality of their tests, we will obviously do what we can to support it.

smyth64 commented 4 years ago

First I was really excited about using playwright instead of puppeteer. But not being able to connect to existing browsers and using their context/pages really disappoints me ☹️

arjunattam commented 4 years ago

Thanks for the feedback @smyth64! We are actively exploring this space. Can you please elaborate on your use-case/goals? How would this feature help your goals?

smyth64 commented 4 years ago

I have a system where I connect to different websites at the same time and do some interaction on them based on the user input.

Sometimes the websites need to load initially very long. That’s why I want to reconnect to the puppets instead of opening the site every time again.

Arjun Attam notifications@github.com schrieb am Mo. 25. Mai 2020 um 03:11:

Thanks for the feedback @smyth64 https://github.com/smyth64! We are actively exploring this space. Can you please elaborate on your use-case/goals? How would this feature help your goals?

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/microsoft/playwright/issues/1985#issuecomment-633279857, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAYYVPQBUN6UMAYZBTV6LSDRTFWNTANCNFSM4MQ4Y6EA .

GkqqNkKC commented 4 years ago

Sry for bumping in, but this problem (not being able to access contexts not made by playwright) hits me hard too. I was a puppeteer user similar to @smyth64 . I came here as I was looking for an alternative as puppteer had some issues and the people behind it doesn't pay too much attention to the issues opened by users

playwright seems wonderful, that's the reason I'm here. Currently I use these remote controllers to automate jobs, and doing pentesting in particular. Both tasks requires to use the already loaded context (as I'm logged in accounts; cookies are in place; sometimes I need to start the automation script mid-session)

I understand that playwright shouldn't manage contexts not made by it. In this case what about mark it as an experimental feature? The user is responsible of it's usage. I also deduce from here that puppeteer is not safe with its control

benjamingr commented 4 years ago

@GkqqNkKC as a workaround (I hope they support this feature obviously :]) you can open Chrome with playwright (that opens it with a debugging port) and then control the session with selenium (You can attach to an existing session).

(Also you can also pretty quickly patch playwright's code to use the already existing Context, but I would not recommend that route :])

GkqqNkKC commented 4 years ago

@benjamingr , thx for workarounds. I will make use of them until new solutions arrive

pavelfeldman commented 4 years ago

I'll tentatively close this as won't fix. It is essential for Playwright to be able to run the browser in the controlled environment. There are a lot of good hints here on how this can be worked around, but it is unlikely that they get into the official Playwright API.

cybairfly commented 3 years ago

Well, this feature would be really helpful along with the PW CLI since it creates a separate browser instance and context of its own. Any way to access and control the context of the CLI tool codegen?

jancurn commented 3 years ago

+1

benjamingr commented 3 years ago

If anyone cares (I'm not with Testim.io anymore so I won't work on tools relating to this most likely) there is code at https://github.com/testimio/root-cause that achieves this with the internal API IIRC, note it's locked to a specific playwright version range (look at the history in order to see how it changes).

cybairfly commented 3 years ago

Thanks a lot for sharing! @benjamingr will sure have a look.

It would be still great for PW to provide access to the context handle directly.

dmitrysteblyuk commented 3 years ago

This is possible with connectOverCdp() since v1.9.0.

The default context is accessible now and it has pages there too! 🎉

I wish I also knew the difference between connectOverCdp and connnect (when connecting to chromium) and why it's working for one and not the other, but the documentation doesn't say much about this (and it's not clear from looking into the code too).

mxschmitt commented 3 years ago

browserType.connectOverCDP = uses Chrome CDP protocol and works by that only with Chromium based browsers browserType.connect = uses Playwright own protocol and works with all Playwright browsers

dmitrysteblyuk commented 3 years ago

browserType.connect = uses Playwright own protocol and works with all Playwright browsers

this is the most confusing part. Do you mean Playwright browsers (which are just latest builds according to docs) implement Playwright own protocol? This doesn't seem likely.

mxschmitt commented 3 years ago

connect is the connection side where you can connect to a server which got launched via launchServer, see here as an example. It works with Chromium, Firefox, and WebKit: https://playwright.dev/docs/api/class-browsertype#browser-type-launch-server

Their version depends on which version you have installed. Its always using the pinned version from the NPM package and release-notes.

dmitrysteblyuk commented 3 years ago

But this is perfect, thank you so much! Now it's all clear. So I can connect multiple playwright clients to the same playwright server (even if the latter is launched with remote-debugging-pipe). Can I use different playwright versions for instantiating clients, and a different version of playwright for launching the server? Will they always work together if they have the same major version?

mxschmitt commented 2 years ago

Can I use different playwright versions for instantiating clients, and a different version of playwright for launching the server?

We only guarantee that the same Playwright client version works with the same Playwright server version. Usually if they are the same minor version they work together.


Locking the issue for now to reduce the noise for the other folks, for further questions, please create new individual issues.