• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

microlinkhq / browserless / 26123057333

19 May 2026 08:23PM UTC coverage: 75.817% (+0.07%) from 75.752%
26123057333

push

github

web-flow
perf(function)!: skip browser for non-page functions (#770)

* perf(function)!: skip browser for non-page functions

Non-page functions (`() => 420`, `({name}) => name`) no longer
  open a browser context, navigate, or consume a concurrency slot.
  Only functions referencing `page` go through the browser path.

  BREAKING CHANGE: non-page functions no longer receive `device`
  or `response` — both are `undefined`. Functions that need HTTP
  response data must destructure `page` to trigger the browser
  path.

* fix: addressing comment

400 of 477 branches covered (83.86%)

Branch coverage included in aggregate %.

33 of 33 new or added lines in 1 file covered. (100.0%)

3 existing lines in 1 file now uncovered.

1989 of 2674 relevant lines covered (74.38%)

187363.95 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.68
/packages/function/src/index.js
1
'use strict'
8✔
2

8✔
3
const { isBrowserlessError, ensureError } = require('@browserless/errors')
8✔
4
const createIsolatedFunction = require('isolated-function')
8✔
5
const requireOneOf = require('require-one-of')
8✔
6
const createRunFunction = require('./function')
8✔
7

8✔
8
const stringify = fn => fn.toString().trim().replace(/;$/, '')
8✔
9

8✔
10
const getTargetId = async page => {
8✔
11
  try {
21✔
12
    const session = await page.createCDPSession()
21✔
13
    const { targetInfo } = await session.send('Target.getTargetInfo')
16✔
14
    await session.detach()
16✔
15
    return targetInfo.targetId
16✔
16
  } catch {
21✔
17
    return undefined
5✔
18
  }
5✔
19
}
21✔
20

8✔
21
const serializeResponse = response => ({
8✔
22
  status: response.status(),
17✔
23
  statusText: response.statusText(),
17✔
24
  url: response.url(),
17✔
25
  ok: response.ok(),
17✔
26
  headers: response.headers(),
17✔
27
  remoteAddress: response.remoteAddress(),
17✔
28
  timing: response.timing(),
17✔
29
  fromCache: response.fromCache(),
17✔
30
  fromServiceWorker: response.fromServiceWorker()
17✔
31
})
17✔
32

8✔
33
module.exports = ({ tmpdir } = {}) => {
8✔
34
  const isolatedFunction = createIsolatedFunction({ tmpdir })
8✔
35
  const runFunction = createRunFunction(isolatedFunction)
8✔
36

8✔
37
  const createFunction = (
8✔
38
    fn,
30✔
39
    {
30✔
40
      getBrowserless = requireOneOf(['browserless']),
30✔
41
      retry = 2,
30✔
42
      timeout = 30000,
30✔
43
      gotoOpts,
30✔
44
      ...opts
30✔
45
    } = {}
30✔
46
  ) => {
30✔
47
    const code = stringify(fn)
30✔
48
    const needsNetwork = createRunFunction.isUsingPage(code)
30✔
49
    const source = createRunFunction.buildTemplate(code, needsNetwork)
30✔
50
    let browserPromise
30✔
51

30✔
52
    const getBrowser = async () => {
30✔
53
      if (!browserPromise) {
22✔
54
        browserPromise = Promise.resolve(getBrowserless()).catch(error => {
21✔
55
          browserPromise = undefined
1✔
56
          throw error
1✔
57
        })
21✔
58
      }
21✔
59
      return browserPromise
22✔
60
    }
22✔
61

30✔
62
    const runWithBrowser = async (url, fnOpts) => {
30✔
63
      const browser = await getBrowser()
22✔
64
      const browserless = await browser.createContext()
21✔
65

21✔
66
      return browserless.withPage((page, goto) => async () => {
21✔
67
        const { device, response } = await goto(page, { url, timeout, ...gotoOpts })
21✔
68

21✔
69
        const targetId = await getTargetId(page)
21✔
70

21✔
71
        const runFunctionOpts = {
21✔
72
          url,
21✔
73
          code,
21✔
74
          device,
21✔
75
          ...opts,
21✔
76
          ...fnOpts,
21✔
77
          ...(response && { _response: serializeResponse(response) })
21✔
78
        }
21✔
79

21✔
80
        if (runFunctionOpts.code === code) {
21✔
81
          runFunctionOpts.needsNetwork = needsNetwork
21✔
82
          runFunctionOpts.source = source
21✔
83
        }
21✔
84

21✔
85
        const browserFromPage = typeof page.browser === 'function' ? page.browser() : undefined
21!
86
        const browserWSEndpoint =
21✔
87
          browserFromPage && typeof browserFromPage.wsEndpoint === 'function'
21✔
88
            ? browserFromPage.wsEndpoint()
21✔
89
            : undefined
21!
90

21✔
91
        if (!browserWSEndpoint) throw new Error('Browser WebSocket endpoint not found')
21✔
92
        runFunctionOpts.browserWSEndpoint = browserWSEndpoint
20✔
93
        runFunctionOpts.targetId = targetId
20✔
94

20✔
95
        const result = await runFunction(runFunctionOpts)
20✔
96

20✔
97
        if (result.isFulfilled) return result
20✔
UNCOV
98
        const error = ensureError(result.value)
×
UNCOV
99
        if (isBrowserlessError(error)) throw error
×
UNCOV
100
        return result
×
101
      })()
21✔
102
    }
22✔
103

30✔
104
    const runWithoutBrowser = async (url, fnOpts) => {
30✔
105
      const runFunctionOpts = {
12✔
106
        url,
12✔
107
        code,
12✔
108
        ...opts,
12✔
109
        ...fnOpts
12✔
110
      }
12✔
111

12✔
112
      if (runFunctionOpts.code === code) {
12✔
113
        runFunctionOpts.needsNetwork = false
12✔
114
        runFunctionOpts.source = source
12✔
115
      }
12✔
116

12✔
117
      const result = await runFunction(runFunctionOpts)
12✔
118

12✔
119
      if (result.isFulfilled) return result
12✔
120
      const error = ensureError(result.value)
1✔
121
      if (isBrowserlessError(error)) throw error
7!
122
      return result
1✔
123
    }
12✔
124

30✔
125
    return async (url, fnOpts = {}) =>
30✔
126
      needsNetwork ? runWithBrowser(url, fnOpts) : runWithoutBrowser(url, fnOpts)
34✔
127
  }
30✔
128

8✔
129
  createFunction.teardown = () => isolatedFunction.teardown()
8✔
130

8✔
131
  return createFunction
8✔
132
}
8✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc