Skip to content

Commit

Permalink
feat: support loading page functions from files using webpack (#19)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
pimlie committed Aug 15, 2019
1 parent 6708fbd commit 5f3322b
Show file tree
Hide file tree
Showing 79 changed files with 2,154 additions and 390 deletions.
24 changes: 19 additions & 5 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,19 @@ jobs:
name: Unit Tests
command: yarn test:unit --coverage && yarn coverage

test-e2e-chrome:
docker:
- image: circleci/node:latest-browsers
steps:
- checkout
- attach_workspace:
at: ~/project
- run:
name: E2E Tests
command: yarn test:e2e --coverage && yarn coverage
environment:
BROWSER_STRING: chrome

test-e2e-firefox:
docker:
- image: circleci/node:latest-browsers
Expand All @@ -78,7 +91,7 @@ jobs:
environment:
BROWSER_STRING: firefox

test-e2e-chrome:
test-e2e-puppeteer:
docker:
- image: circleci/node:latest-browsers
steps:
Expand All @@ -89,7 +102,7 @@ jobs:
name: E2E Tests
command: yarn test:e2e --coverage && yarn coverage
environment:
BROWSER_STRING: chrome/selenium
BROWSER_STRING: puppeteer

test-e2e-browserstack:
docker:
Expand Down Expand Up @@ -126,7 +139,8 @@ workflows:
- lint: { requires: [setup] }
- audit: { requires: [setup] }
- test-unit: { requires: [lint] }
- test-e2e-firefox: { requires: [lint] }
- test-e2e-chrome: { requires: [lint] }
- test-e2e-browserstack: { requires: [lint] }
- test-e2e-jsdom: { requires: [lint] }
- test-e2e-chrome: { requires: [lint] }
- test-e2e-firefox: { requires: [lint] }
- test-e2e-jsdom: { requires: [lint] }
- test-e2e-puppeteer: { requires: [lint] }
2 changes: 1 addition & 1 deletion .eslintignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
node_modules
coverage
test/fixtures/**
src/browsers.js
src/browsers/index.js
4 changes: 1 addition & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,10 @@ Helper classes for e2e browser testing in Node with a uniform interface.

## Introduction

`tib` aims to provide a uniform interface for testing in both Puppeteer and Selenium while using either local browsers or any available 3rd party provider. This way you can write a single e2e test and simply switch the browser environment by changing the [`BrowserString`](#browser-strings)
`tib` aims to provide a uniform interface for testing with both jsdom, Puppeteer and Selenium while using either local browsers or a 3rd party provider. This way you can write a single e2e test and simply switch the browser environment by changing the [`BrowserString`](#browser-strings)

The term `helper classes` stems from that this package wont enforce test functionality on you (which would require another learning curve). `tib` allows you to use the test suite you are already familair with. Use `tib` to retrieve and assert whether the html you expect to be loaded is really loaded, both on page load as after interacting with it through javascript.

This probably means that `tib` is deliberately less integrated then other packages.

## Supported browsers/drivers/providers:

- Puppeteer
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,12 @@
"dependencies": {
"@babel/core": "^7.5.5",
"@babel/parser": "^7.5.5",
"babel-loader": "^8.0.6",
"hable": "^1.0.1",
"signal-exit": "^3.0.2",
"tree-kill": "^1.2.1",
"vue-template-compiler": "^2.6.10"
"vue-template-compiler": "^2.6.10",
"webpack": "^4.39.1"
},
"devDependencies": {
"@babel/node": "^7.5.5",
Expand Down
4 changes: 2 additions & 2 deletions scripts/create-browsers.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const Glob = require('glob')
const glob = promisify(Glob)

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

fs.writeFileSync(path.join(__dirname, '../src/browsers.js'), `/**
fs.writeFileSync(path.join(__dirname, '../src/browsers/index.js'), `/**
* THIS FILE IS AUTOMATICALLY GENERATED
* DONT CHANGE ANYTHING MANUALLY
*/
Expand Down
10 changes: 5 additions & 5 deletions src/browser.js → src/browsers/browser.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import path from 'path'
import Hookable from 'hable'
import onExit from 'signal-exit'
import BrowserError from './utils/error'
import { Xvfb, StaticServer } from './utils/commands'
import { browsers } from './browsers'
import { Xvfb, StaticServer } from '../commands'
import {
abstractGuard,
loadDependency,
isMockedFunction,
disableTimers,
enableTimers,
getBrowserConfigFromString,
getBrowserImportFromConfig
} from './utils'
getBrowserImportFromConfig,
BrowserError
} from '../utils'
import { browsers } from '.'

export default class Browser extends Hookable {
constructor(config = {}) {
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import BrowserStackLocal from '../utils/commands/browserstack-local'
import BrowserStackLocal from '../../commands/browserstack-local'
import BrowserStackBrowser from './'

export default class BrowserStackLocalBrowser extends BrowserStackBrowser {
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
4 changes: 2 additions & 2 deletions src/chrome/selenium.js → src/browsers/chrome/selenium.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import ChromeDetector from '../utils/detectors/chrome'
import ChromeDetector from '../../utils/detectors/chrome'
import SeleniumBrowser from '../selenium'
import SeleniumLogging from '../selenium/logging'
import BrowserError from '../utils/error'
import { BrowserError } from '../../utils'

export default class ChromeSeleniumBrowser extends SeleniumLogging(SeleniumBrowser) {
constructor(config) {
Expand Down
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.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
60 changes: 41 additions & 19 deletions src/jsdom/webpage.js → src/browsers/jsdom/webpage.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,7 @@
import BrowserError from '../utils/error'
import { BrowserError } from '../../utils'
import Webpage from '../webpage'

export default class JsdomWebpage extends Webpage {
async wrapWithGlobals(fn) {
global.window = this.window
global.document = this.document

const ret = await fn()

delete global.window
delete global.document

return ret
}

async open(url, readyCondition = 'body') {
const jsdomOpts = this.browser.config.jsdom || {}

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

if (options.virtualConsole === 'trues') {
if (options.virtualConsole === true) {
const logLevels = this.browser.logLevels

const pageConsole = new Proxy({}, {
Expand Down Expand Up @@ -113,11 +101,34 @@ export default class JsdomWebpage extends Webpage {
return this.returnProxy()
}

runScript(fn, ...args) {
return this.wrapWithGlobals(() => fn(...args))
async wrapWithGlobals(fn) {
global.window = this.window
global.document = this.document

const ret = await fn()

delete global.window
delete global.document

return ret
}

getBabelPresetOptions(...args) {
const presetOptions = super.getBabelPresetOptions(...args)

presetOptions.targets = {
node: 'current'
}

return presetOptions
}

runAsyncScript(fn, ...args) {
runScript(fn, ...args) {
if (typeof fn === 'object') {
// eslint-disable-next-line no-new-func
fn = new Function(...fn.args, fn.body)
}

return this.wrapWithGlobals(() => fn(...args))
}

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

return Promise.resolve(pageFunction(el, ...args))
return this.wrapWithGlobals(() => pageFunction(el, ...args))
}

getElementsFromPage(pageFunction, selector, ...args) {
const els = Array.from(this.document.querySelectorAll(selector))
return Promise.resolve(pageFunction(els, ...args))
return this.wrapWithGlobals(() => pageFunction(els, ...args))
}

clickElement(selector) {
/* istanbul ignore next */
const pageFn = (el) => {
const event = new window.Event('click')
el.dispatchEvent(event)

return new Promise(resolve => setTimeout(resolve, 500))
}
return this.getElementFromPage(pageFn, selector)
}
}
4 changes: 2 additions & 2 deletions src/puppeteer/core.js → src/browsers/puppeteer/core.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import ChromeDetector from '../utils/detectors/chrome'
import ChromeDetector from '../../utils/detectors/chrome'
import Browser from '../browser'
import BrowserError from '../utils/error'
import { BrowserError } from '../../utils'
import Webpage from './webpage'

export default class PuppeteerCoreBrowser extends Browser {
Expand Down
File renamed without changes.
49 changes: 49 additions & 0 deletions src/browsers/puppeteer/webpage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import Webpage from '../webpage'
import { parseFunction } from '../../utils'

export default class PuppeteerWebpage extends Webpage {
async open(url, readyCondition = 'body') {
this.page = await this.driver.newPage()

await this.browser.callHook('page:created', this.page)

await this.page.goto(url)

if (readyCondition) {
await this.page.waitFor(readyCondition)
}

return this.returnProxy()
}

runScript(pageFunction, ...args) {
let parsedFn
if (typeof pageFunction === 'function') {
parsedFn = parseFunction(pageFunction, args, this.getBabelPresetOptions())
} else {
parsedFn = pageFunction
}

// It would be bettter to return undefined when no el exists,
// but selenium always returns null for undefined so better to keep
// the return value consistent
return this.page.evaluate(
/* istanbul ignore next */
function (fn, ...args) {
return (new (Function.bind.apply(Function, fn))()).apply(null, [].concat(args))
},
[null, ...parsedFn.args, parsedFn.body],
...args
)
}

getHtml() {
/* istanbul ignore next */
const pageFn = () => window.document.documentElement.outerHTML
return this.page.evaluate(pageFn)
}

getTitle() {
return this.page.title()
}
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
2 changes: 1 addition & 1 deletion src/saucelabs/index.js → src/browsers/saucelabs/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import SeleniumBrowser from '../selenium'
import SeleniumLogging from '../selenium/logging'
import BrowserError from '../utils/error'
import { BrowserError } from '../../utils'

export default class SauceLabsBrowser extends SeleniumLogging(SeleniumBrowser) {
/* istanbul ignore next */
Expand Down
File renamed without changes.
File renamed without changes.
21 changes: 16 additions & 5 deletions src/selenium/webpage.js → src/browsers/selenium/webpage.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Webpage from '../webpage'
import { parseFunction } from '../utils'
import { parseFunction } from '../../utils'

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

runScript(fn, ...args) {
const parsedFn = parseFunction(fn, this.getBabelPresetOptions())
let parsedFn
if (typeof fn === 'function') {
parsedFn = parseFunction(fn, this.getBabelPresetOptions())
} else {
parsedFn = fn
}

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

runAsyncScript(fn, ...args) {
const parsedFn = parseFunction(fn, this.getBabelPresetOptions())
let parsedFn
if (typeof fn === 'function') {
parsedFn = parseFunction(fn, this.getBabelPresetOptions())
} else {
parsedFn = fn
}

const argStr = parsedFn.args.reduce((acc, v, i) => `${acc}var ${v} = arguments[${i}]; `, '')
const script = `${argStr}
var callback = arguments[arguments.length - 1];
var retVal = (function() { ${parsedFn.body} })()
var args = [].slice.call(arguments)
var callback = args.pop()
var retVal = (function() { ${parsedFn.body} }).apply(null, args)
if (retVal && retVal.then) {
retVal.then(callback)
} else {
Expand Down
9 changes: 6 additions & 3 deletions src/webpage.js → src/browsers/webpage.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import BrowserError from './utils/error'
import { abstractGuard, parseFunction, getDefaultHtmlCompiler } from './utils'
import { BrowserError, abstractGuard, parseFunction, getDefaultHtmlCompiler } from '../utils'

export default class Webpage {
constructor(browser) {
Expand Down Expand Up @@ -58,7 +57,7 @@ export default class Webpage {
return this._htmlCompiler
}

getBabelPresetOptions() {
getBabelPresetOptions({ polyfills = false } = {}) {
const presetOptions = {}

const browser = this.browser.getBrowser()
Expand All @@ -70,6 +69,10 @@ export default class Webpage {
}
}

if (polyfills) {
presetOptions.useBuiltIns = polyfills === true ? 'usage' : polyfills
}

return presetOptions
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import path from 'path'
import kill from 'tree-kill'
import onExit from 'signal-exit'
import { loadDependency } from '..'
import { loadDependency } from '../utils'

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

Expand Down Expand Up @@ -55,6 +55,7 @@ export default class BrowserStackLocal {
// practically the same
kill(pid, 'SIGTERM', (error) => {
if (error) {
/* istanbul ignore next */
reject(error)
}

Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import http from 'http'
import { loadDependency } from '..'
import { loadDependency } from '../utils'

export default class StaticServer {
static async loadDependencies() {
Expand Down
Loading

0 comments on commit 5f3322b

Please sign in to comment.