UNPKG

ghostjs

Version:

Modern web integration test runner

308 lines (278 loc) 7.16 kB
var driver = require('node-phantom-simple') var argv = require('yargs').argv import Element from './element'; class Ghost { constructor () { this.testRunner = argv['ghost-runner'] || 'phantomjs-prebuilt' this.driverOpts = null this.setDriverOpts({}) this.browser = null this.page = null this.childPages = [] this.clientScripts = [] } /** * Sets options object that is used in driver creation. */ setDriverOpts (opts) { this.driverOpts = this.testRunner.match(/phantom/) ? opts : {} this.driverOpts.path = require(this.testRunner).path // The dnode `weak` dependency is failing to install on travis. // Disable this for now until someone needs it. this.driverOpts.dnodeOpts = { weak: false } } /** * Adds scripts to be injected to for each page load. * Should be called before ghost#open. */ injectScripts () { Array.slice(arguments).forEach(script => { this.clientScripts.push(script) }) } /** * Callback when a page loads. * Injects javascript and other things we need. */ onOpen () { // Inject any client scripts this.clientScripts.forEach(script => { this.page.injectJs(script) }) } async open (url) { // If we already have a page object, just navigate it. if (this.page) { return new Promise(resolve => { this.page.open(url, (err, status) => { this.onOpen() resolve(status) }) }) } return new Promise(resolve => { driver.create(this.driverOpts, (err, browser) => { this.browser = browser browser.createPage((err, page) => { this.page = page; page.onPageCreated = (page) => { var pageObj = { page: page, url: null } this.childPages.push(pageObj) page.onUrlChanged = (url) => { pageObj.url = url; }; } page.onConsoleMessage = (msg) => { if (argv['verbose']) { console.log('[Console]', msg) } } page.open(url, (err, status) => { this.onOpen() resolve(status) }) }) }) }) } close () { if (this.page) { this.page.close() } this.page = null } async exit () { this.close() this.browser.exit() this.browser = null } goBack () { this.page.goBack() } goForward () { this.page.goForward() } screenshot (filename, folder='screenshots') { filename = filename || 'screenshot-' + Date.now() this.page.render(`${folder}/${filename}.png`) } /** * Returns the title of the current page. */ async pageTitle () { return new Promise(resolve => { this.page.evaluate(() => { return document.title }, (err, result) => { resolve(result) }) }) } /** * Waits for the page title to match a given state. */ async waitForPageTitle (expected) { var waitFor = this.waitFor.bind(this) var pageTitle = this.pageTitle.bind(this) return new Promise(async resolve => { var result = await waitFor(async () => { var title = await pageTitle() if (expected instanceof RegExp) { return expected.test(title) } else { return title === expected } }) resolve(result) }) } /** * Returns an element if it finds it in the page, otherwise returns null. */ async findElement (selector) { return new Promise(resolve => { this.page.evaluate((selector) => { return document.querySelector(selector) }, selector, (err, result) => { if (!result) { return resolve(null) } resolve(new Element(this.page, selector)) }) }) } /** * Returns all elements that match the current selector in the page. */ async countElements (selector) { return new Promise(resolve => { this.page.evaluate((selector) => { return document.querySelectorAll(selector).length }, selector, (err, result) => { resolve(result) }) }) } /** * Resizes the page to a desired width and height. */ async resize (width, height) { this.page.set('viewportSize', {width, height}) } /** * Executes a script within the page. */ async script (func, args) { if (!Array.isArray(args)) { args = [args] } return new Promise(resolve => { this.page.evaluate((stringyFunc, args) => { var invoke = new Function( "return " + stringyFunc )(); return invoke.apply(null, args) }, func.toString(), args, (err, result) => { resolve(result) }) }) } /** * Waits for an arbitrary amount of time. */ async wait (time=1000) { return new Promise(resolve => { setTimeout(resolve, time) }) } /** * Waits for an element to exist in the page. */ async waitForElement (selector) { // Scoping gets broken within async promises, so bind these locally. var waitFor = this.waitFor.bind(this) var findElement = this.findElement.bind(this) return new Promise(async resolve => { var element = await waitFor(async () => { var el = await findElement(selector) if (el) { return el } return false }) resolve(element) }) } /** * Waits for an element to be hidden, or removed from the dom. */ async waitForElementNotVisible (selector) { var waitFor = this.waitFor.bind(this) var findElement = this.findElement.bind(this) return new Promise(async resolve => { var isHidden = await waitFor(async () => { var el = await findElement(selector) return !el || !await el.isVisible() }) resolve(isHidden) }) } /** * Waits for an element to exist, and be visible. */ async waitForElementVisible (selector) { var waitFor = this.waitFor.bind(this) var findElement = this.findElement.bind(this) return new Promise(async resolve => { var isVisible = await waitFor(async () => { var el = await findElement(selector) return el && await el.isVisible() }) resolve(isVisible) }) } /** * Waits for a child page to be loaded. */ waitForPage (url) { var waitFor = this.waitFor.bind(this) var childPages = this.childPages return new Promise(async resolve => { var isFound = await waitFor(async () => { return childPages.filter(val => { return val.url.includes(url) }).length > 0 }) resolve(isFound) }) } /** * Waits for a condition to be met */ async waitFor (func, pollMs=100) { return new Promise(resolve => { var poll = async () => { var result = await func() if (result) { resolve(result) } else { setTimeout(poll, pollMs) } } poll() }) } } var ghost = new Ghost() export default ghost