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

feat(browser): Run puppeteer in browser (POC) #2374

Closed
wants to merge 6 commits into from

Conversation

Janpot
Copy link
Contributor

@Janpot Janpot commented Apr 13, 2018

Proof of concept for #2119

@aslushnikov
Copy link
Contributor

@Janpot thank you for the PR, it turned out to be quite minimalistic.

I evaluate this PR as a "what needs to be changed in pptr so that this can be implemented as a third-party library". From this standpoint, I see two things we need to address:

  • helper.projectRoot() clearly stands on your way
  • you'd like to pass your own transport implementation in puppeteer.connect. This way you can make it through a websocket (like you did) or over the chrome.debugger in the extension-land.

@JoelEinbinder claimed he has another approach that achieves the same results. Joel, can you please share your code?

@Janpot
Copy link
Contributor Author

Janpot commented Apr 21, 2018

@aslushnikov
"I evaluate this PR as ..." That is a good assessment. I posted the issue initially when I was exploring an idea but currently I have no immediate use-case anymore. I'm still interested in this as a thought experiment though. And I don't mind following up on this PR. That being said:

  1. The projectroot thing is problematic as it seems to be only there to solve the node6 thing being under a different path. It's solvable in several ways. I just propose one in this PR, but there's other possibilities. It definitely needs a refactor if we want to support browsers.
  2. Yep, passing a transport makes sense. I could also make it work by adapting the way ws is consumed and simply swapping it out for the browser WebSocket. ws supports both EventTarget and EventEmitter interface.

@ChristosChristofidis
Copy link

why would you want to run in from the browser?

@Janpot
Copy link
Contributor Author

Janpot commented May 11, 2018

For instance (but not limited to): https://chrome.browserless.io/

@wKich
Copy link

wKich commented May 30, 2018

Hello. For some reason I want to do fair actions (clicks, typing, etc) in my functional tests. So let me share my experience to run pptr in browser without any changes of original source code.

For websocket.js I took @Janpot's code.
dummy.js is a simple module that exports empty object.

// webpack.config.js
module.exports = {
  /* ... */
  resolve: {
    alias: {
      ws: path.join(__dirname, 'helpers/websocket.js'),
      fs: path.join(__dirname, 'helpers/dummy.js'),
    }
  }
}
// chrome-devtools.js
import { Connection } from 'puppeteer/lib/Connection'
import Browser from 'puppeteer/lib/Browser'
import uuidv4 from 'uuid/v4'

let attachPromise = null
const id = uuidv4()

export default function attachDevTools() {
  if (attachPromise) return attachPromise

  attachPromise = fetch(`/attach?id=${id}`)
    .then(res => res.json())
    .then(async ({ targetInfo, browserDebuggerUrl }) => {
      const { targetId } = targetInfo

      window.addEventListener('beforeunload', () => fetch(`/detach?targetId=${targetId}`))

      const connection = await Connection.createForWebSocket(browserDebuggerUrl)
      const browser = await Browser.create(connection, { appMode: true })
      const pages = await browser.pages()

      // NOTE We don't want to crash browser under events flood
      // Disables network tracking, prevents network events from being sent to the client
      pages.forEach(p => p._client.send('Network.disable', {}))

      return pages.find(p => p.target()._targetId == targetId)
    })

  return attachPromise
}

That's issues I faced:

  1. I can't send http request to http://localhost:9222/json endpoint directly from a browser, so I use simple express server as workaround.
  2. I don't know how to find out targetId for the page from which I want to connect. But in my scenario this is not such big problem, because I always have 1 active tab.

If you agree with that usage of pptr, maybe I can do PR to make this in proper way

@wKich
Copy link

wKich commented May 31, 2018

Ah, about 1st issue, I didn’t know that chrome have command line option to disable cors security checks, so proxy server is not needed.

And another issue, I think could solve through sending guid to the console, for example, and check every page to find that guid.

@wKich
Copy link

wKich commented Jun 1, 2018

I rewrite attach function without express server and without using targetId

import { Connection } from 'puppeteer/lib/Connection'
import Browser from 'puppeteer/lib/Browser'
import uuidv4 from 'uuid/v4'

export default async function attachDevTools() {
  const response = await fetch('http://localhost:9222/json/version')
  const data = await response.json()

  const connection = await Connection.createForWebSocket(data.webSocketDebuggerUrl)
  const browser = await Browser.create(connection, { appMode: true })
  const pages = await browser.pages()

  // Disables network tracking, prevents network events from being sent to the client
  // NOTE Because we don't want crash browser under events flood
  pages.forEach(p => p._client.send('Network.disable', {}))

  return new Promise(resolve => {
    const expectedGuid = uuid()
    const findCurrentPage = index => async msg => {
      if (msg.type() != 'debug') return

      const [firstArg] = msg.args()
      const receivedGuid = await firstArg.jsonValue()

      if (receivedGuid == expectedGuid) {
        resolve(pages[index])
      }
    }

    pages.forEach((p, index) => p.once('console', findCurrentPage(index)))
    console.debug(expectedGuid)
  })
}

@Janpot Janpot changed the title Run puppeteer in browser (POC) feat(browser): Run puppeteer in browser (POC) Jun 1, 2018
aslushnikov added a commit to aslushnikov/puppeteer that referenced this pull request Sep 6, 2018
This patch removes all dynamic requires in Puppeteer. This should
make it much simpler to bundle puppeteer/puppeteer-core packages.

We used dynamic requires in a few places in lib/:
- BrowserFetcher was choosing between `http` and `https` based on some
  runtime value. This was easy to fix with explicit `require`.
- BrowserFetcher and Launcher needed to know project root to store
  chromium revisions and to read package name and chromium revision from
  package.json. (projectRoot value would be different in node6).
  Instead of doing a backwards logic to infer these
  variables, we now pass them directly from `//index.js`.

With this patch, I was able to bundle Puppeteer using browserify and
the following config in `package.json`:

```json
  "browser": {
    "./lib/BrowserFetcher.js": false,
    "ws": "./lib/BrowserWebSocket",
    "fs": false,
    "child_process": false,
    "rimraf": false,
    "readline": false
  }
```

(where `lib/BrowserWebSocket.js` is a courtesy of @Janpot from
puppeteer#2374)

And command:

```sh
$ browserify -r puppeteer:./index.js > ppweb.js
```

References puppeteer#2119
aslushnikov added a commit that referenced this pull request Sep 6, 2018
This patch removes all dynamic requires in Puppeteer. This should
make it much simpler to bundle puppeteer/puppeteer-core packages.

We used dynamic requires in a few places in lib/:
- BrowserFetcher was choosing between `http` and `https` based on some
  runtime value. This was easy to fix with explicit `require`.
- BrowserFetcher and Launcher needed to know project root to store
  chromium revisions and to read package name and chromium revision from
  package.json. (projectRoot value would be different in node6).
  Instead of doing a backwards logic to infer these
  variables, we now pass them directly from `//index.js`.

With this patch, I was able to bundle Puppeteer using browserify and
the following config in `package.json`:

```json
  "browser": {
    "./lib/BrowserFetcher.js": false,
    "ws": "./lib/BrowserWebSocket",
    "fs": false,
    "child_process": false,
    "rimraf": false,
    "readline": false
  }
```

(where `lib/BrowserWebSocket.js` is a courtesy of @Janpot from
#2374)

And command:

```sh
$ browserify -r puppeteer:./index.js > ppweb.js
```

References #2119
aslushnikov added a commit to aslushnikov/puppeteer that referenced this pull request Sep 13, 2018
This patch:
- adds "browser" field to the package.json with default
  bundling options.
- introduces "bundle" and "unit-bundle" commands to
  create bundle and test bundle
- starts running bundle tests on Travis Node 8 bots

Fixes puppeteer#2374.
@aslushnikov
Copy link
Contributor

Closing in favor of #3239

aslushnikov added a commit that referenced this pull request Sep 13, 2018
This patch:
- adds "browser" field to the package.json with default
  bundling options.
- introduces "bundle" and "unit-bundle" commands to
  create bundle and test bundle
- starts running bundle tests on Travis Node 8 bots

Fixes #2374.
@Janpot Janpot deleted the browser branch October 8, 2018 12:06
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.

None yet

4 participants