Skip to content

Commit 5f3322b

Browse files
authored
feat: support loading page functions from files using webpack (#19)
* feat: support loading page functions directly from files (using webpack) refactor: improve folder structure * chore: remove unused dependency * dont automatically set babel presets if no presets are provided, fallback to using babelrc config * print warning when a page function already exists * if sourceFiles is a string, assume its a glob path * use limit chunk count webpack plugin * prefer finding node_modules relative from dist dir * refactor BrowserError to be a named export * use array index to access first char * use createCacheKey also for parseFunction * increase test coverage * ignore coverage of deprecated method * fix/improve page function tests * determine valid cache correctly * try enabling puppeteer on ci again * always use tib for cache path improve warning msg * fix test
1 parent 6708fbd commit 5f3322b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

79 files changed

+2154
-390
lines changed

.circleci/config.yml

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,19 @@ jobs:
6565
name: Unit Tests
6666
command: yarn test:unit --coverage && yarn coverage
6767

68+
test-e2e-chrome:
69+
docker:
70+
- image: circleci/node:latest-browsers
71+
steps:
72+
- checkout
73+
- attach_workspace:
74+
at: ~/project
75+
- run:
76+
name: E2E Tests
77+
command: yarn test:e2e --coverage && yarn coverage
78+
environment:
79+
BROWSER_STRING: chrome
80+
6881
test-e2e-firefox:
6982
docker:
7083
- image: circleci/node:latest-browsers
@@ -78,7 +91,7 @@ jobs:
7891
environment:
7992
BROWSER_STRING: firefox
8093

81-
test-e2e-chrome:
94+
test-e2e-puppeteer:
8295
docker:
8396
- image: circleci/node:latest-browsers
8497
steps:
@@ -89,7 +102,7 @@ jobs:
89102
name: E2E Tests
90103
command: yarn test:e2e --coverage && yarn coverage
91104
environment:
92-
BROWSER_STRING: chrome/selenium
105+
BROWSER_STRING: puppeteer
93106

94107
test-e2e-browserstack:
95108
docker:
@@ -126,7 +139,8 @@ workflows:
126139
- lint: { requires: [setup] }
127140
- audit: { requires: [setup] }
128141
- test-unit: { requires: [lint] }
129-
- test-e2e-firefox: { requires: [lint] }
130-
- test-e2e-chrome: { requires: [lint] }
131142
- test-e2e-browserstack: { requires: [lint] }
132-
- test-e2e-jsdom: { requires: [lint] }
143+
- test-e2e-chrome: { requires: [lint] }
144+
- test-e2e-firefox: { requires: [lint] }
145+
- test-e2e-jsdom: { requires: [lint] }
146+
- test-e2e-puppeteer: { requires: [lint] }

.eslintignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
node_modules
22
coverage
33
test/fixtures/**
4-
src/browsers.js
4+
src/browsers/index.js

README.md

Lines changed: 1 addition & 3 deletions

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,12 @@
3636
"dependencies": {
3737
"@babel/core": "^7.5.5",
3838
"@babel/parser": "^7.5.5",
39+
"babel-loader": "^8.0.6",
3940
"hable": "^1.0.1",
4041
"signal-exit": "^3.0.2",
4142
"tree-kill": "^1.2.1",
42-
"vue-template-compiler": "^2.6.10"
43+
"vue-template-compiler": "^2.6.10",
44+
"webpack": "^4.39.1"
4345
},
4446
"devDependencies": {
4547
"@babel/node": "^7.5.5",

scripts/create-browsers.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const Glob = require('glob')
88
const glob = promisify(Glob)
99

1010
async function main() {
11-
const srcPath = path.resolve(__dirname, '../src/') + '/'
11+
const srcPath = path.resolve(__dirname, '../src/browsers') + '/'
1212
let files = await glob(`${srcPath}/!(utils)/**/*.js`)
1313
files = files
1414
.filter(f => !f.includes('webpage') && !f.includes('logging'))
@@ -25,7 +25,7 @@ async function main() {
2525
`
2626
}, '')
2727

28-
fs.writeFileSync(path.join(__dirname, '../src/browsers.js'), `/**
28+
fs.writeFileSync(path.join(__dirname, '../src/browsers/index.js'), `/**
2929
* THIS FILE IS AUTOMATICALLY GENERATED
3030
* DONT CHANGE ANYTHING MANUALLY
3131
*/

src/browser.js renamed to src/browsers/browser.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
import path from 'path'
22
import Hookable from 'hable'
33
import onExit from 'signal-exit'
4-
import BrowserError from './utils/error'
5-
import { Xvfb, StaticServer } from './utils/commands'
6-
import { browsers } from './browsers'
4+
import { Xvfb, StaticServer } from '../commands'
75
import {
86
abstractGuard,
97
loadDependency,
108
isMockedFunction,
119
disableTimers,
1210
enableTimers,
1311
getBrowserConfigFromString,
14-
getBrowserImportFromConfig
15-
} from './utils'
12+
getBrowserImportFromConfig,
13+
BrowserError
14+
} from '../utils'
15+
import { browsers } from '.'
1616

1717
export default class Browser extends Hookable {
1818
constructor(config = {}) {
File renamed without changes.

src/browserstack/local.js renamed to src/browsers/browserstack/local.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import BrowserStackLocal from '../utils/commands/browserstack-local'
1+
import BrowserStackLocal from '../../commands/browserstack-local'
22
import BrowserStackBrowser from './'
33

44
export default class BrowserStackLocalBrowser extends BrowserStackBrowser {
File renamed without changes.
File renamed without changes.

src/chrome/selenium.js renamed to src/browsers/chrome/selenium.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import ChromeDetector from '../utils/detectors/chrome'
1+
import ChromeDetector from '../../utils/detectors/chrome'
22
import SeleniumBrowser from '../selenium'
33
import SeleniumLogging from '../selenium/logging'
4-
import BrowserError from '../utils/error'
4+
import { BrowserError } from '../../utils'
55

66
export default class ChromeSeleniumBrowser extends SeleniumLogging(SeleniumBrowser) {
77
constructor(config) {
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

src/jsdom/webpage.js renamed to src/browsers/jsdom/webpage.js

Lines changed: 41 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,7 @@
1-
import BrowserError from '../utils/error'
1+
import { BrowserError } from '../../utils'
22
import Webpage from '../webpage'
33

44
export default class JsdomWebpage extends Webpage {
5-
async wrapWithGlobals(fn) {
6-
global.window = this.window
7-
global.document = this.document
8-
9-
const ret = await fn()
10-
11-
delete global.window
12-
delete global.document
13-
14-
return ret
15-
}
16-
175
async open(url, readyCondition = 'body') {
186
const jsdomOpts = this.browser.config.jsdom || {}
197

@@ -37,7 +25,7 @@ export default class JsdomWebpage extends Webpage {
3725
throw new BrowserError(this, err)
3826
}
3927

40-
if (options.virtualConsole === 'trues') {
28+
if (options.virtualConsole === true) {
4129
const logLevels = this.browser.logLevels
4230

4331
const pageConsole = new Proxy({}, {
@@ -113,11 +101,34 @@ export default class JsdomWebpage extends Webpage {
113101
return this.returnProxy()
114102
}
115103

116-
runScript(fn, ...args) {
117-
return this.wrapWithGlobals(() => fn(...args))
104+
async wrapWithGlobals(fn) {
105+
global.window = this.window
106+
global.document = this.document
107+
108+
const ret = await fn()
109+
110+
delete global.window
111+
delete global.document
112+
113+
return ret
114+
}
115+
116+
getBabelPresetOptions(...args) {
117+
const presetOptions = super.getBabelPresetOptions(...args)
118+
119+
presetOptions.targets = {
120+
node: 'current'
121+
}
122+
123+
return presetOptions
118124
}
119125

120-
runAsyncScript(fn, ...args) {
126+
runScript(fn, ...args) {
127+
if (typeof fn === 'object') {
128+
// eslint-disable-next-line no-new-func
129+
fn = new Function(...fn.args, fn.body)
130+
}
131+
121132
return this.wrapWithGlobals(() => fn(...args))
122133
}
123134

@@ -135,11 +146,22 @@ export default class JsdomWebpage extends Webpage {
135146
return Promise.resolve(null)
136147
}
137148

138-
return Promise.resolve(pageFunction(el, ...args))
149+
return this.wrapWithGlobals(() => pageFunction(el, ...args))
139150
}
140151

141152
getElementsFromPage(pageFunction, selector, ...args) {
142153
const els = Array.from(this.document.querySelectorAll(selector))
143-
return Promise.resolve(pageFunction(els, ...args))
154+
return this.wrapWithGlobals(() => pageFunction(els, ...args))
155+
}
156+
157+
clickElement(selector) {
158+
/* istanbul ignore next */
159+
const pageFn = (el) => {
160+
const event = new window.Event('click')
161+
el.dispatchEvent(event)
162+
163+
return new Promise(resolve => setTimeout(resolve, 500))
164+
}
165+
return this.getElementFromPage(pageFn, selector)
144166
}
145167
}

src/puppeteer/core.js renamed to src/browsers/puppeteer/core.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import ChromeDetector from '../utils/detectors/chrome'
1+
import ChromeDetector from '../../utils/detectors/chrome'
22
import Browser from '../browser'
3-
import BrowserError from '../utils/error'
3+
import { BrowserError } from '../../utils'
44
import Webpage from './webpage'
55

66
export default class PuppeteerCoreBrowser extends Browser {
File renamed without changes.

src/browsers/puppeteer/webpage.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import Webpage from '../webpage'
2+
import { parseFunction } from '../../utils'
3+
4+
export default class PuppeteerWebpage extends Webpage {
5+
async open(url, readyCondition = 'body') {
6+
this.page = await this.driver.newPage()
7+
8+
await this.browser.callHook('page:created', this.page)
9+
10+
await this.page.goto(url)
11+
12+
if (readyCondition) {
13+
await this.page.waitFor(readyCondition)
14+
}
15+
16+
return this.returnProxy()
17+
}
18+
19+
runScript(pageFunction, ...args) {
20+
let parsedFn
21+
if (typeof pageFunction === 'function') {
22+
parsedFn = parseFunction(pageFunction, args, this.getBabelPresetOptions())
23+
} else {
24+
parsedFn = pageFunction
25+
}
26+
27+
// It would be bettter to return undefined when no el exists,
28+
// but selenium always returns null for undefined so better to keep
29+
// the return value consistent
30+
return this.page.evaluate(
31+
/* istanbul ignore next */
32+
function (fn, ...args) {
33+
return (new (Function.bind.apply(Function, fn))()).apply(null, [].concat(args))
34+
},
35+
[null, ...parsedFn.args, parsedFn.body],
36+
...args
37+
)
38+
}
39+
40+
getHtml() {
41+
/* istanbul ignore next */
42+
const pageFn = () => window.document.documentElement.outerHTML
43+
return this.page.evaluate(pageFn)
44+
}
45+
46+
getTitle() {
47+
return this.page.title()
48+
}
49+
}
File renamed without changes.
File renamed without changes.

src/saucelabs/index.js renamed to src/browsers/saucelabs/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import SeleniumBrowser from '../selenium'
22
import SeleniumLogging from '../selenium/logging'
3-
import BrowserError from '../utils/error'
3+
import { BrowserError } from '../../utils'
44

55
export default class SauceLabsBrowser extends SeleniumLogging(SeleniumBrowser) {
66
/* istanbul ignore next */
File renamed without changes.
File renamed without changes.

src/selenium/webpage.js renamed to src/browsers/selenium/webpage.js

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import Webpage from '../webpage'
2-
import { parseFunction } from '../utils'
2+
import { parseFunction } from '../../utils'
33

44
export default class SeleniumWebpage extends Webpage {
55
async open(url, readyCondition = 'body') {
@@ -26,7 +26,12 @@ export default class SeleniumWebpage extends Webpage {
2626
}
2727

2828
runScript(fn, ...args) {
29-
const parsedFn = parseFunction(fn, this.getBabelPresetOptions())
29+
let parsedFn
30+
if (typeof fn === 'function') {
31+
parsedFn = parseFunction(fn, this.getBabelPresetOptions())
32+
} else {
33+
parsedFn = fn
34+
}
3035

3136
const argStr = parsedFn.args.reduce((acc, v, i) => `${acc}var ${v} = arguments[${i}]; `, '')
3237
const script = `${argStr}
@@ -36,12 +41,18 @@ export default class SeleniumWebpage extends Webpage {
3641
}
3742

3843
runAsyncScript(fn, ...args) {
39-
const parsedFn = parseFunction(fn, this.getBabelPresetOptions())
44+
let parsedFn
45+
if (typeof fn === 'function') {
46+
parsedFn = parseFunction(fn, this.getBabelPresetOptions())
47+
} else {
48+
parsedFn = fn
49+
}
4050

4151
const argStr = parsedFn.args.reduce((acc, v, i) => `${acc}var ${v} = arguments[${i}]; `, '')
4252
const script = `${argStr}
43-
var callback = arguments[arguments.length - 1];
44-
var retVal = (function() { ${parsedFn.body} })()
53+
var args = [].slice.call(arguments)
54+
var callback = args.pop()
55+
var retVal = (function() { ${parsedFn.body} }).apply(null, args)
4556
if (retVal && retVal.then) {
4657
retVal.then(callback)
4758
} else {

src/webpage.js renamed to src/browsers/webpage.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import BrowserError from './utils/error'
2-
import { abstractGuard, parseFunction, getDefaultHtmlCompiler } from './utils'
1+
import { BrowserError, abstractGuard, parseFunction, getDefaultHtmlCompiler } from '../utils'
32

43
export default class Webpage {
54
constructor(browser) {
@@ -58,7 +57,7 @@ export default class Webpage {
5857
return this._htmlCompiler
5958
}
6059

61-
getBabelPresetOptions() {
60+
getBabelPresetOptions({ polyfills = false } = {}) {
6261
const presetOptions = {}
6362

6463
const browser = this.browser.getBrowser()
@@ -70,6 +69,10 @@ export default class Webpage {
7069
}
7170
}
7271

72+
if (polyfills) {
73+
presetOptions.useBuiltIns = polyfills === true ? 'usage' : polyfills
74+
}
75+
7376
return presetOptions
7477
}
7578

src/utils/commands/browserstack-local.js renamed to src/commands/browserstack-local.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import path from 'path'
22
import kill from 'tree-kill'
33
import onExit from 'signal-exit'
4-
import { loadDependency } from '..'
4+
import { loadDependency } from '../utils'
55

66
const consola = console // eslint-disable-line no-console
77

@@ -55,6 +55,7 @@ export default class BrowserStackLocal {
5555
// practically the same
5656
kill(pid, 'SIGTERM', (error) => {
5757
if (error) {
58+
/* istanbul ignore next */
5859
reject(error)
5960
}
6061

File renamed without changes.

src/utils/commands/static-server.js renamed to src/commands/static-server.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import http from 'http'
2-
import { loadDependency } from '..'
2+
import { loadDependency } from '../utils'
33

44
export default class StaticServer {
55
static async loadDependencies() {

0 commit comments

Comments
 (0)