UNPKG

@revoloo/cypress6

Version:

Cypress.io end to end testing tool

251 lines (208 loc) 6.48 kB
const map = require('lodash/map') const pick = require('lodash/pick') const once = require('lodash/once') const Promise = require('bluebird') const browser = require('webextension-polyfill') const client = require('./client') const { getCookieUrl } = require('../lib/util') const COOKIE_PROPS = ['url', 'name', 'path', 'secure', 'domain'] const GET_ALL_PROPS = COOKIE_PROPS.concat(['session', 'storeId']) // https://developer.chrome.com/extensions/cookies#method-set const SET_PROPS = COOKIE_PROPS.concat(['value', 'httpOnly', 'expirationDate', 'sameSite']) const httpRe = /^http/ // normalize into null when empty array const firstOrNull = (cookies) => { return cookies[0] != null ? cookies[0] : null } const connect = function (host, path, extraOpts) { const listenToCookieChanges = once(() => { return browser.cookies.onChanged.addListener((info) => { if (info.cause !== 'overwrite') { return ws.emit('automation:push:request', 'change:cookie', info) } }) }) const listenToDownloads = once(() => { browser.downloads.onCreated.addListener((downloadItem) => { ws.emit('automation:push:request', 'create:download', { id: `${downloadItem.id}`, filePath: downloadItem.filename, mime: downloadItem.mime, url: downloadItem.url, }) }) browser.downloads.onChanged.addListener((downloadDelta) => { if ((downloadDelta.state || {}).current !== 'complete') return ws.emit('automation:push:request', 'complete:download', { id: `${downloadDelta.id}`, }) }) }) const fail = (id, err) => { return ws.emit('automation:response', id, { __error: err.message, __stack: err.stack, __name: err.name, }) } const invoke = function (method, id, ...args) { const respond = (data) => { return ws.emit('automation:response', id, { response: data }) } return Promise.try(() => { return automation[method].apply(automation, args.concat(respond)) }).catch((err) => { return fail(id, err) }) } const ws = client.connect(host, path, extraOpts) ws.on('automation:request', (id, msg, data) => { switch (msg) { case 'get:cookies': return invoke('getCookies', id, data) case 'get:cookie': return invoke('getCookie', id, data) case 'set:cookie': return invoke('setCookie', id, data) case 'clear:cookies': return invoke('clearCookies', id, data) case 'clear:cookie': return invoke('clearCookie', id, data) case 'is:automation:client:connected': return invoke('verify', id, data) case 'focus:browser:window': return invoke('focus', id) case 'take:screenshot': return invoke('takeScreenshot', id) default: return fail(id, { message: `No handler registered for: '${msg}'` }) } }) ws.on('connect', () => { listenToCookieChanges() listenToDownloads() return ws.emit('automation:client:connected') }) return ws } const automation = { connect, getUrl: getCookieUrl, clear (filter = {}) { const clear = (cookie) => { const url = this.getUrl(cookie) const props = { url, name: cookie.name } const throwError = function (err) { throw (err != null ? err : new Error(`Removing cookie failed for: ${JSON.stringify(props)}`)) } return Promise.try(() => { return browser.cookies.remove(props) }).then((details) => { if (details) { return cookie } return throwError() }).catch(throwError) } return this.getAll(filter) .map(clear) }, getAll (filter = {}) { filter = pick(filter, GET_ALL_PROPS) return Promise.try(() => { return browser.cookies.getAll(filter) }) }, getCookies (filter, fn) { return this.getAll(filter) .then(fn) }, getCookie (filter, fn) { return this.getAll(filter) .then(firstOrNull) .then(fn) }, setCookie (props = {}, fn) { // only get the url if its not already set if (props.url == null) { props.url = this.getUrl(props) } if (props.hostOnly) { delete props.domain } if (props.domain === 'localhost') { delete props.domain } props = pick(props, SET_PROPS) return Promise.try(() => { return browser.cookies.set(props) // the cookie callback could be null such as the // case when expirationDate is before now }).then((details) => { return fn(details || null) }) }, clearCookie (filter, fn) { return this.clear(filter) .then(firstOrNull) .then(fn) }, clearCookies (filter, fn) { return this.clear(filter) .then(fn) }, focus (fn) { // lets just make this simple and whatever is the current // window bring that into focus // // TODO: if we REALLY want to be nice its possible we can // figure out the exact window that's running Cypress but // that's too much work with too little value at the moment return Promise.try(() => { return browser.windows.getCurrent() }).then((window) => { return browser.windows.update(window.id, { focused: true }) }).then(fn) }, query (data) { const code = `var s; (s = document.getElementById('${data.element}')) && s.textContent` const queryTab = (tab) => { return Promise.try(() => { return browser.tabs.executeScript(tab.id, { code }) }).then((results) => { if (!results || (results[0] !== data.string)) { throw new Error('Executed script did not return result') } }) } return Promise.try(() => { return browser.tabs.query({ windowType: 'normal' }) }).filter((tab) => { // the tab's url must begin with // http or https so that we filter out // about:blank and chrome:// urls // which will throw errors! return httpRe.test(tab.url) }).then((tabs) => { // generate array of promises return map(tabs, queryTab) }).any() }, verify (data, fn) { return this.query(data) .then(fn) }, lastFocusedWindow () { return Promise.try(() => { return browser.windows.getLastFocused() }) }, takeScreenshot (fn) { return this.lastFocusedWindow() .then((win) => { return browser.tabs.captureVisibleTab(win.id, { format: 'png' }) }) .then(fn) }, } module.exports = automation