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

microlinkhq / browserless / 30891946126

04 Aug 2026 08:26AM UTC coverage: 80.583% (+1.9%) from 78.732%
30891946126

Pull #869

github

web-flow
Merge d0274c65e into 0bb8df180
Pull Request #869: fix: context leaks, screenshot readiness, and prepare scroll

725 of 886 branches covered (81.83%)

Branch coverage included in aggregate %.

44 of 47 new or added lines in 4 files covered. (93.62%)

3836 of 4774 relevant lines covered (80.35%)

104958.52 hits per line

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

96.05
/packages/function/src/index.js
1
'use strict'
9✔
2

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

9✔
9
const cloudflareDir = path.dirname(require.resolve('@cloudflare/puppeteer/package.json'))
9✔
10
const nodePaths = [path.resolve(cloudflareDir, '..', '..')]
9✔
11

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

9✔
14
const getTargetId = async page => {
9✔
15
  try {
21✔
16
    const session = await page.createCDPSession()
21✔
17
    const { targetInfo } = await session.send('Target.getTargetInfo')
16✔
18
    await session.detach()
16✔
19
    return targetInfo.targetId
16✔
20
  } catch {
21✔
21
    return undefined
5✔
22
  }
5✔
23
}
21✔
24

9✔
25
const isHttpResponse = response => response != null && typeof response.status === 'function'
9✔
26

9✔
27
const serializeResponse = response => ({
9✔
28
  status: response.status(),
17✔
29
  statusText: response.statusText(),
17✔
30
  url: response.url(),
17✔
31
  ok: response.ok(),
17✔
32
  headers: response.headers(),
17✔
33
  remoteAddress: response.remoteAddress(),
17✔
34
  timing: response.timing(),
17✔
35
  fromCache: response.fromCache(),
17✔
36
  fromServiceWorker: response.fromServiceWorker()
17✔
37
})
17✔
38

9✔
39
module.exports = ({ tmpdir } = {}) => {
9✔
40
  const isolatedFunction = createIsolatedFunction({ tmpdir, nodePaths })
9✔
41
  const runFunction = createRunFunction(isolatedFunction)
9✔
42

9✔
43
  const createFunction = (
9✔
44
    fn,
31✔
45
    {
31✔
46
      getBrowserless = requireOneOf(['browserless']),
31✔
47
      retry = 2,
31✔
48
      timeout = 30000,
31✔
49
      gotoOpts,
31✔
50
      ...opts
31✔
51
    } = {}
31✔
52
  ) => {
31✔
53
    const code = stringify(fn)
31✔
54
    const needsNetwork = createRunFunction.isUsingPage(code)
31✔
55
    const source = createRunFunction.buildTemplate(code, needsNetwork)
31✔
56
    let browserPromise
31✔
57

31✔
58
    const getBrowser = async () => {
31✔
59
      if (!browserPromise) {
23✔
60
        browserPromise = Promise.resolve(getBrowserless()).catch(error => {
22✔
61
          browserPromise = undefined
1✔
62
          throw error
1✔
63
        })
22✔
64
      }
22✔
65
      return browserPromise
23✔
66
    }
23✔
67

31✔
68
    const runWithBrowser = async (url, fnOpts) => {
31✔
69
      const browser = await getBrowser()
23✔
70
      const browserless = await browser.createContext()
22✔
71

22✔
72
      try {
22✔
73
        return await browserless.withPage((page, goto) => async () => {
22✔
74
          const { device, response } = await goto(page, { url, timeout, ...gotoOpts })
21✔
75

21✔
76
          const targetId = await getTargetId(page)
21✔
77

21✔
78
          const runFunctionOpts = {
21✔
79
            url,
21✔
80
            code,
21✔
81
            device,
21✔
82
            ...opts,
21✔
83
            ...fnOpts,
21✔
84
            ...(isHttpResponse(response) && { _response: serializeResponse(response) })
21✔
85
          }
21✔
86

21✔
87
          if (runFunctionOpts.code === code) {
21✔
88
            runFunctionOpts.needsNetwork = needsNetwork
21✔
89
            runFunctionOpts.source = source
21✔
90
          }
21✔
91

21✔
92
          const browserFromPage = typeof page.browser === 'function' ? page.browser() : undefined
21!
93
          const browserWSEndpoint =
21✔
94
            browserFromPage && typeof browserFromPage.wsEndpoint === 'function'
21✔
95
              ? browserFromPage.wsEndpoint()
21✔
96
              : undefined
21!
97

21✔
98
          if (!browserWSEndpoint) throw new Error('Browser WebSocket endpoint not found')
21✔
99
          runFunctionOpts.browserWSEndpoint = browserWSEndpoint
20✔
100
          runFunctionOpts.targetId = targetId
20✔
101

20✔
102
          const result = await runFunction(runFunctionOpts)
20✔
103

20✔
104
          if (result.isFulfilled) return result
20✔
NEW
105
          const error = ensureError(result.value)
×
NEW
106
          if (isBrowserlessError(error)) throw error
×
NEW
107
          return result
×
108
        })()
22✔
109
      } finally {
22✔
110
        await browserless.destroyContext()
22✔
111
      }
22✔
112
    }
23✔
113

31✔
114
    const runWithoutBrowser = async (url, fnOpts) => {
31✔
115
      const runFunctionOpts = {
12✔
116
        url,
12✔
117
        code,
12✔
118
        ...opts,
12✔
119
        ...fnOpts
12✔
120
      }
12✔
121

12✔
122
      if (runFunctionOpts.code === code) {
12✔
123
        runFunctionOpts.needsNetwork = false
12✔
124
        runFunctionOpts.source = source
12✔
125
      }
12✔
126

12✔
127
      const result = await runFunction(runFunctionOpts)
12✔
128

12✔
129
      if (result.isFulfilled) return result
12✔
130
      const error = ensureError(result.value)
1✔
131
      if (isBrowserlessError(error)) throw error
7!
132
      return result
1✔
133
    }
12✔
134

31✔
135
    return async (url, fnOpts = {}) =>
31✔
136
      needsNetwork ? runWithBrowser(url, fnOpts) : runWithoutBrowser(url, fnOpts)
35✔
137
  }
31✔
138

9✔
139
  createFunction.teardown = () => isolatedFunction.teardown()
9✔
140

9✔
141
  return createFunction
9✔
142
}
9✔
143

9✔
144
module.exports.isHttpResponse = isHttpResponse
9✔
145
module.exports.serializeResponse = serializeResponse
9✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc