From 065eb6084cf14d7d09045a4caf0cdf6199f45101 Mon Sep 17 00:00:00 2001 From: angrykoala Date: Sat, 27 Jul 2019 14:46:13 +0200 Subject: [PATCH 1/7] version update --- CHANGELOG.md | 3 +++ package.json | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3710330c..089bb174 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +2.5.2 / ####-##-## +================== + 2.5.1 / 2019-07-27 ================== diff --git a/package.json b/package.json index bd0c947d..82a55aca 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wendigo", - "version": "2.5.1", + "version": "2.5.2", "description": "A proper monster for front-end automated testing", "engines": { "node": ">=8.0.0" From 4816fd49b394e2cfa60c5474f8dfdaf180654d21 Mon Sep 17 00:00:00 2001 From: angrykoala Date: Mon, 29 Jul 2019 18:10:05 +0200 Subject: [PATCH 2/7] Return newlines on
for browser.text, close #379 --- CHANGELOG.md | 2 ++ lib/browser/mixins/browser_info.ts | 6 +++--- tests/assertions/assert_text.test.js | 10 ++++++++++ tests/browser/text.test.js | 9 ++++++++- tests/dummy_server/static/weird_text.html | 1 + 5 files changed, 24 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 089bb174..81a774cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ 2.5.2 / ####-##-## ================== +* Browser.text will return newlines on
+ 2.5.1 / 2019-07-27 ================== diff --git a/lib/browser/mixins/browser_info.ts b/lib/browser/mixins/browser_info.ts index ce557526..09116e14 100644 --- a/lib/browser/mixins/browser_info.ts +++ b/lib/browser/mixins/browser_info.ts @@ -8,12 +8,12 @@ import { PDFOptions } from '../../puppeteer_wrapper/puppeteer_types'; export default abstract class BrowserInfo extends BrowserClick { @FailIfNotLoaded - public text(selector: WendigoSelector): Promise> { - return this.evaluate((q) => { + public async text(selector: WendigoSelector): Promise> { + return await this.evaluate((q) => { const elements = WendigoUtils.queryAll(q); const result = []; for (const e of elements) { - result.push(e.textContent); + result.push(e.innerText); } return result; }, selector); diff --git a/tests/assertions/assert_text.test.js b/tests/assertions/assert_text.test.js index 3b968b0d..b76ed913 100644 --- a/tests/assertions/assert_text.test.js +++ b/tests/assertions/assert_text.test.js @@ -179,4 +179,14 @@ describe("Assert Text", function() { await browser.assert.not.text(".empty-p", ""); }, `[assert.not.text] Expected element ".empty-p" not to have text "".`); }); + + it("Button Text", async() => { + await browser.open(configUrls.click); + await browser.assert.text(".btn", "click me"); + }); + + it("Text With
", async() => { + await browser.open(configUrls.weirdText); + await browser.assert.text(".text-br", "This is\na text"); + }); }); diff --git a/tests/browser/text.test.js b/tests/browser/text.test.js index 57c39bda..dba1560d 100644 --- a/tests/browser/text.test.js +++ b/tests/browser/text.test.js @@ -45,6 +45,13 @@ describe("Text", function() { it("Button Text", async() => { await browser.open(configUrls.click); - await browser.assert.text(".btn", "click me"); + const text = await browser.text(".btn"); + assert.strictEqual(text[0], "click me"); + }); + + it("Text With
", async() => { + await browser.open(configUrls.weirdText); + const text = await browser.text(".text-br"); + assert.strictEqual(text[0], "This is\na text"); }); }); diff --git a/tests/dummy_server/static/weird_text.html b/tests/dummy_server/static/weird_text.html index 198d87f2..9430cd1d 100644 --- a/tests/dummy_server/static/weird_text.html +++ b/tests/dummy_server/static/weird_text.html @@ -4,5 +4,6 @@

"quotes'

colon:

"quo'tes''

+

This is
a text

From 161907c105081a4022d8ee1f74cd0bb65b660f18 Mon Sep 17 00:00:00 2001 From: angrykoala Date: Mon, 5 Aug 2019 01:35:01 +0200 Subject: [PATCH 3/7] default timeout for wait methods, close #409 --- CHANGELOG.md | 3 ++- README.md | 1 + lib/browser/browser.ts | 2 +- lib/browser/mixins/browser_wait.ts | 31 +++++++++++++++++------- lib/modules/requests/browser_requests.ts | 19 +++++++++++---- lib/types.ts | 13 +++++++++- lib/wendigo.ts | 7 +++--- package.json | 2 +- 8 files changed, 57 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 81a774cc..4f369ca7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,8 @@ -2.5.2 / ####-##-## +2.6.0 / ####-##-## ================== * Browser.text will return newlines on
+* DefaultTimeout option on create browser 2.5.1 / 2019-07-27 ================== diff --git a/README.md b/README.md index ee2bca10..69c90bc7 100644 --- a/README.md +++ b/README.md @@ -152,6 +152,7 @@ Will create and return a promise to a [Browser](#Browser) instance. It will auto * `dismissAllDialogs`: This will automatically dismiss any native dialog (`alert`, `prompt`) when appearing. * `bypassCSP: true`: If set to false, puppeteer may fail if Content Security Policy is set in the page. * `proxyServer: null`: If defined, Chromium will run with the option `--proxy-server` set to the given address. + * `defaultTimeout: 500`: Sets the default timeout for "wait" methods, except `browser.wait()`. * `cache: true`: If true, requests cache will be enabled. * Any settings that can be passed to Puppeteer can be passed to createBrowser, for example: * `headless: true`: If true, the browser will run on headless mode. diff --git a/lib/browser/browser.ts b/lib/browser/browser.ts index 3d0e8b00..0102702c 100644 --- a/lib/browser/browser.ts +++ b/lib/browser/browser.ts @@ -26,7 +26,7 @@ export default class Browser extends BrowserTap { super(context, page, settings, components); this.cookies = new BrowserCookies(this); this.localStorage = new BrowserLocalStorage(this); - this.requests = new BrowserRequests(this); + this.requests = new BrowserRequests(this, settings); this.console = new BrowserConsole(this); this.webworkers = new BrowserWebworker(this); this.dialog = new BrowserDialog(this); diff --git a/lib/browser/mixins/browser_wait.ts b/lib/browser/mixins/browser_wait.ts index 450cfce2..18af36d5 100644 --- a/lib/browser/mixins/browser_wait.ts +++ b/lib/browser/mixins/browser_wait.ts @@ -2,7 +2,7 @@ import BrowserNavigation from './browser_navigation'; import DomElement from '../../models/dom_element'; import { TimeoutError, WendigoError } from '../../errors'; import { WendigoSelector } from '../../types'; -import { createFindTextXPath, delay } from '../../utils/utils'; +import { createFindTextXPath, delay, isNumber } from '../../utils/utils'; import FailIfNotLoaded from '../../decorators/fail_if_not_loaded'; import OverrideError from '../../decorators/override_error'; import { EvaluateFn } from '../../puppeteer_wrapper/puppeteer_types'; @@ -14,7 +14,8 @@ export default abstract class BrowserWait extends BrowserNavigation { } @FailIfNotLoaded - public async waitFor(selector: EvaluateFn, timeout = 500, ...args: Array): Promise { + public async waitFor(selector: EvaluateFn, timeout?: number, ...args: Array): Promise { + timeout = this._getTimeout(timeout); args = args.map((e) => { if (e instanceof DomElement) return e.element; else return e; @@ -33,7 +34,8 @@ export default abstract class BrowserWait extends BrowserNavigation { } @FailIfNotLoaded - public async waitUntilNotVisible(selector: WendigoSelector, timeout = 500): Promise { + public async waitUntilNotVisible(selector: WendigoSelector, timeout?: number): Promise { + timeout = this._getTimeout(timeout); try { await this.waitFor((q: string | HTMLElement) => { const element = WendigoUtils.queryElement(q); @@ -45,7 +47,8 @@ export default abstract class BrowserWait extends BrowserNavigation { } @FailIfNotLoaded - public async waitForUrl(url: string | RegExp, timeout: number = 500): Promise { + public async waitForUrl(url: string | RegExp, timeout?: number): Promise { + timeout = this._getTimeout(timeout); if (!url) return Promise.reject(new WendigoError("waitForUrl", `Invalid parameter url.`)); let parsedUrl: string | RegExp | { source: string, flags: string } = url; if (url instanceof RegExp) { @@ -71,7 +74,8 @@ export default abstract class BrowserWait extends BrowserNavigation { } @FailIfNotLoaded - public async waitForNavigation(timeout: number = 500): Promise { + public async waitForNavigation(timeout?: number): Promise { + timeout = this._getTimeout(timeout); const t1 = new Date().getTime(); try { await this._page.waitForNavigation({ @@ -92,7 +96,8 @@ export default abstract class BrowserWait extends BrowserNavigation { @FailIfNotLoaded @OverrideError() - public async clickAndWaitForNavigation(selector: WendigoSelector, timeout: number = 500): Promise { + public async clickAndWaitForNavigation(selector: WendigoSelector, timeout?: number): Promise { + timeout = this._getTimeout(timeout); const result = await Promise.all([ this.waitForNavigation(timeout), this.click(selector) @@ -100,7 +105,8 @@ export default abstract class BrowserWait extends BrowserNavigation { return result[1]; } - public async waitForText(text: string, timeout: number = 500): Promise { + public async waitForText(text: string, timeout?: number): Promise { + timeout = this._getTimeout(timeout); try { const xPath = createFindTextXPath(text); await this.waitFor((xp: string) => { @@ -111,7 +117,8 @@ export default abstract class BrowserWait extends BrowserNavigation { } } - public async waitAndClick(selector: string, timeout: number = 500): Promise { + public async waitAndClick(selector: string, timeout?: number): Promise { + timeout = this._getTimeout(timeout); try { await this.waitFor(selector, timeout); return await this.click(selector); @@ -120,7 +127,8 @@ export default abstract class BrowserWait extends BrowserNavigation { } } - public async waitUntilEnabled(selector: WendigoSelector, timeout: number = 500): Promise { + public async waitUntilEnabled(selector: WendigoSelector, timeout?: number): Promise { + timeout = this._getTimeout(timeout); try { await this.waitFor((q: string | HTMLElement) => { const element = WendigoUtils.queryElement(q); @@ -132,4 +140,9 @@ export default abstract class BrowserWait extends BrowserNavigation { throw new TimeoutError("waitUntilEnabled", `Waiting for element "${selector}" to be enabled`, timeout); } } + + private _getTimeout(timeout?: number): number { + if (isNumber(timeout)) return timeout; + else return this._settings.defaultTimeout; + } } diff --git a/lib/modules/requests/browser_requests.ts b/lib/modules/requests/browser_requests.ts index 5970ddf3..cfb04ed4 100644 --- a/lib/modules/requests/browser_requests.ts +++ b/lib/modules/requests/browser_requests.ts @@ -1,5 +1,5 @@ import WendigoModule from '../wendigo_module'; -import { OpenSettings } from '../../types'; +import { OpenSettings, FinalBrowserSettings } from '../../types'; import RequestFilter from './request_filter'; import RequestMocker from './request_mocker'; @@ -8,19 +8,21 @@ import RequestMock from './request_mock'; import { Request, Response } from '../../puppeteer_wrapper/puppeteer_types'; import { RequestMockOptions } from './types'; import { TimeoutError } from '../../errors'; -import { promiseOr, matchText } from '../../utils/utils'; +import { promiseOr, matchText, isNumber } from '../../utils/utils'; export default class BrowserRequests extends WendigoModule { private _requestMocker: RequestMocker; private _requests: Array; private _interceptorReady: boolean; private _interceptorCallback?: (req: Request) => Promise; + private _settings: FinalBrowserSettings; - constructor(browser: Browser) { + constructor(browser: Browser, settings: FinalBrowserSettings) { super(browser); this._requestMocker = new RequestMocker(); this._requests = []; this._interceptorReady = false; + this._settings = settings; this.clearRequests(); } @@ -52,7 +54,8 @@ export default class BrowserRequests extends WendigoModule { this._requestMocker.clear(); } - public async waitForNextRequest(url: string | RegExp, timeout: number = 500): Promise { + public async waitForNextRequest(url: string | RegExp, timeout?: number): Promise { + timeout = this._getTimeout(timeout); try { await this._waitForRequestEvent("request", url, timeout); } catch (err) { @@ -60,7 +63,8 @@ export default class BrowserRequests extends WendigoModule { } } - public async waitForNextResponse(url: string | RegExp, timeout: number = 500): Promise { + public async waitForNextResponse(url: string | RegExp, timeout?: number): Promise { + timeout = this._getTimeout(timeout); try { await this._waitForRequestEvent("response", url, timeout); } catch (err) { @@ -157,4 +161,9 @@ export default class BrowserRequests extends WendigoModule { this._page.on(event, waitForEventCallback); }); } + + private _getTimeout(timeout?: number): number { + if (isNumber(timeout)) return timeout; + else return this._settings.defaultTimeout; + } } diff --git a/lib/types.ts b/lib/types.ts index 5dba3428..9ef2826e 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -15,9 +15,20 @@ export interface BrowserSettings { proxyServer?: string | null; timezone?: string; cache?: boolean; + defaultTimeout?: number; } -export interface FinalBrowserSettings extends BrowserSettings { +export interface DefaultBrowserSettings extends BrowserSettings { + log: boolean; + headless: boolean; + args: Array; + incognito: boolean; + noSandbox: boolean; + proxyServer: string | null; + defaultTimeout: number; +} + +export interface FinalBrowserSettings extends DefaultBrowserSettings { __onClose: (a: any) => any; env?: { TZ: string diff --git a/lib/wendigo.ts b/lib/wendigo.ts index 2e9be5ef..bc6cab74 100644 --- a/lib/wendigo.ts +++ b/lib/wendigo.ts @@ -3,11 +3,11 @@ import puppeteer from 'puppeteer'; import { BrowserContext } from './puppeteer_wrapper/puppeteer_types'; import BrowserFactory from './browser_factory'; import * as Errors from './errors'; -import { WendigoPluginInterface, BrowserSettings, FinalBrowserSettings, WendigoPluginAssertionInterface, PluginModule } from './types'; +import { WendigoPluginInterface, BrowserSettings, DefaultBrowserSettings, FinalBrowserSettings, WendigoPluginAssertionInterface, PluginModule } from './types'; import BrowserInterface from './browser/browser_interface'; import PuppeteerContext from './puppeteer_wrapper/puppeteer_context'; -const defaultSettings: BrowserSettings = { +const defaultSettings: DefaultBrowserSettings = { log: false, headless: true, args: [], @@ -15,7 +15,8 @@ const defaultSettings: BrowserSettings = { incognito: false, noSandbox: false, bypassCSP: true, - proxyServer: null + proxyServer: null, + defaultTimeout: 500 }; export default class Wendigo { diff --git a/package.json b/package.json index 82a55aca..8e80e93a 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ }, "devDependencies": { "@types/mocha": "^5.2.7", - "@types/node": "^12.6.8", + "@types/node": "^12.6.9", "@types/puppeteer": "~1.19.0", "basic-auth": "^2.0.1", "eslint": "^6.1.0", From 5071188285b0faf77cadeb42b19f3503f492c98d Mon Sep 17 00:00:00 2001 From: angrykoala Date: Mon, 5 Aug 2019 01:45:12 +0200 Subject: [PATCH 4/7] version update --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8e80e93a..e323c179 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wendigo", - "version": "2.5.2", + "version": "2.6.0", "description": "A proper monster for front-end automated testing", "engines": { "node": ">=8.0.0" From 5db49e8a772b341aee4888923eefc010f98d6775 Mon Sep 17 00:00:00 2001 From: angrykoala Date: Tue, 6 Aug 2019 01:55:25 +0200 Subject: [PATCH 5/7] injected scripts added using evaluate, close #408 --- CHANGELOG.md | 2 + config.ts | 12 -- injection_scripts/selector_finder.js | 261 -------------------------- injection_scripts/selector_finder.ts | 265 +++++++++++++++++++++++++++ injection_scripts/selector_query.js | 94 ---------- injection_scripts/selector_query.ts | 94 ++++++++++ injection_scripts/wendigo_utils.js | 77 -------- injection_scripts/wendigo_utils.ts | 83 +++++++++ lib/browser/browser_core.ts | 35 ++-- tests/browser/open.test.js | 26 ++- 10 files changed, 482 insertions(+), 467 deletions(-) delete mode 100644 config.ts delete mode 100644 injection_scripts/selector_finder.js create mode 100644 injection_scripts/selector_finder.ts delete mode 100644 injection_scripts/selector_query.js create mode 100644 injection_scripts/selector_query.ts delete mode 100644 injection_scripts/wendigo_utils.js create mode 100644 injection_scripts/wendigo_utils.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f369ca7..1ff319d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ * Browser.text will return newlines on
* DefaultTimeout option on create browser +* Injection scripts are now added through evaluate, so are not checked by CSP +* Static config file removed 2.5.1 / 2019-07-27 ================== diff --git a/config.ts b/config.ts deleted file mode 100644 index df0adeb7..00000000 --- a/config.ts +++ /dev/null @@ -1,12 +0,0 @@ -import path from 'path'; - -export default { - injectionScripts: { - path: path.join(__dirname, "..", "injection_scripts"), - files: [ - "selector_query.js", - "wendigo_utils.js", - "selector_finder.js" - ] - } -}; diff --git a/injection_scripts/selector_finder.js b/injection_scripts/selector_finder.js deleted file mode 100644 index 46711d81..00000000 --- a/injection_scripts/selector_finder.js +++ /dev/null @@ -1,261 +0,0 @@ -"use strict"; - -// Based on https://github.com/ChromeDevTools/devtools-frontend/blob/master/front_end/elements/DOMPath.js - -// Copyright 2018 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found at https://github.com/ChromeDevTools/devtools-frontend/blob/master/LICENSE - -// Minor refactor and modifications made by @angrykoala for wendigo - -/* eslint-disable no-continue, complexity */ - -if (!window.WendigoPathFinder) { - class Step { - constructor(value, optimized) { - this.value = value; - this.optimized = optimized || false; - } - - toString() { - return this.value; - } - } - - const _cssPathFinderHelpers = { - _stepPreprocess(node) { - if (node.nodeType !== Node.ELEMENT_NODE) - return null; - - const id = node.getAttribute('id'); - if (id) - return new Step(this.idSelector(id), true); - const nodeNameLower = node.nodeName.toLowerCase(); - if (nodeNameLower === 'body' || nodeNameLower === 'head' || nodeNameLower === 'html') - return new Step(node.localName, true); - const nodeName = node.localName; - - const parent = node.parentNode; - if (!parent || parent.nodeType === Node.DOCUMENT_NODE) - return new Step(nodeName, true); - }, - - _cssPathStep(node, isTargetNode) { - const value = this._stepPreprocess(node); - if (value !== undefined) return value; - - - const parent = node.parentNode; - const nodeName = node.localName; - - const prefixedOwnClassNamesArray = this.prefixedElementClassNames(node); - let needsClassNames = false; - let needsNthChild = false; - let ownIndex = -1; - let elementIndex = -1; - const siblings = parent.children; - for (let i = 0; - (ownIndex === -1 || !needsNthChild) && i < siblings.length; ++i) { - const sibling = siblings[i]; - if (sibling.nodeType !== Node.ELEMENT_NODE) - continue; - elementIndex += 1; - if (sibling === node) { - ownIndex = elementIndex; - continue; - } - if (needsNthChild) - continue; - if (sibling.localName !== nodeName) - continue; - - needsClassNames = true; - const ownClassNames = new Set(prefixedOwnClassNamesArray); - if (!ownClassNames.size) { - needsNthChild = true; - continue; - } - const siblingClassNamesArray = this.prefixedElementClassNames(sibling); - for (let j = 0; j < siblingClassNamesArray.length; ++j) { - const siblingClass = siblingClassNamesArray[j]; - if (!ownClassNames.has(siblingClass)) - continue; - ownClassNames.delete(siblingClass); - if (!ownClassNames.size) { - needsNthChild = true; - break; - } - } - } - - let result = nodeName; - if (isTargetNode && nodeName.toLowerCase() === 'input' && node.getAttribute('type') && !node.getAttribute('id') && - !node.getAttribute('class')) - result += `[type="${ node.getAttribute('type') }"]`; - if (needsNthChild) { - result += `:nth-child(${ ownIndex + 1 })`; - } else if (needsClassNames) { - for (const prefixedName of prefixedOwnClassNamesArray) - result += `.${ this.escapeIdentifierIfNeeded(prefixedName.substr(1))}`; - } - - return new Step(result, false); - }, - prefixedElementClassNames(node) { - const classAttribute = node.getAttribute('class'); - if (!classAttribute) - return []; - - return classAttribute.split(/\s+/g).filter(Boolean).map((name) => { - // The prefix is required to store "__proto__" in a object-based map. - return `$${ name}`; - }); - }, - idSelector(id) { - return `#${ this.escapeIdentifierIfNeeded(id)}`; - }, - escapeIdentifierIfNeeded(ident) { - if (this.isCSSIdentifier(ident)) - return ident; - const shouldEscapeFirst = /^(?:[0-9]|-[0-9-]?)/.test(ident); - const lastIndex = ident.length - 1; - return ident.replace(/./g, (c, i) => { - return ((shouldEscapeFirst && i === 0) || !this.isCSSIdentChar(c)) ? this.escapeAsciiChar(c, i === lastIndex) : c; - }); - }, - escapeAsciiChar(c, isLast) { - return `\\${ this.toHexByte(c) }${isLast ? '' : ' '}`; - }, - toHexByte(c) { - let hexByte = c.charCodeAt(0).toString(16); - if (hexByte.length === 1) - hexByte = `0${ hexByte}`; - return hexByte; - }, - isCSSIdentChar(c) { - if (/[a-zA-Z0-9_-]/.test(c)) - return true; - return c.charCodeAt(0) >= 0xA0; - }, - isCSSIdentifier(value) { - // Double hyphen prefixes are not allowed by specification, but many sites use it. - return /^-{0,2}[a-zA-Z_][a-zA-Z0-9_-]*$/.test(value); - } - }; - const _xPathFinderHelpers = { - _xPathStep(node) { - let ownValue; - const ownIndex = this._xPathIndex(node); - if (ownIndex === -1) - return null; // Error. - - switch (node.nodeType) { - case Node.ELEMENT_NODE: - if (node.getAttribute('id')) - return new Step(`//*[@id="${ node.getAttribute('id') }"]`, true); - ownValue = node.localName; - break; - case Node.ATTRIBUTE_NODE: - ownValue = `@${ node.nodeName}`; - break; - case Node.TEXT_NODE: - case Node.CDATA_SECTION_NODE: - ownValue = 'text()'; - break; - case Node.PROCESSING_INSTRUCTION_NODE: - ownValue = 'processing-instruction()'; - break; - case Node.COMMENT_NODE: - ownValue = 'comment()'; - break; - case Node.DOCUMENT_NODE: - ownValue = ''; - break; - default: - ownValue = ''; - break; - } - - if (ownIndex > 0) - ownValue += `[${ ownIndex }]`; - - return new Step(ownValue, node.nodeType === Node.DOCUMENT_NODE); - }, - _xPathIndex(node) { - const siblings = node.parentNode ? node.parentNode.children : null; - if (!siblings) - return 0; // Root node - no siblings. - let hasSameNamedElements = false; - for (let i = 0; i < siblings.length; ++i) { - if (this.areNodesSimilar(node, siblings[i]) && siblings[i] !== node) { - hasSameNamedElements = true; - break; - } - } - if (!hasSameNamedElements) - return 0; - let ownIndex = 1; // XPath indices start with 1. - for (let i = 0; i < siblings.length; ++i) { - if (this.areNodesSimilar(node, siblings[i])) { - if (siblings[i] === node) - return ownIndex; - ownIndex++; - } - } - return -1; // An error occurred: |node| not found in parent's children. - }, - areNodesSimilar(left, right) { - // Returns -1 in case of error, 0 if no siblings matching the same expression, otherwise. - if (left === right) - return true; - - if (left.nodeType === Node.ELEMENT_NODE && right.nodeType === Node.ELEMENT_NODE) - return left.localName === right.localName; - - if (left.nodeType === right.nodeType) - return true; - - // XPath treats CDATA as text nodes. - const leftType = left.nodeType === Node.CDATA_SECTION_NODE ? Node.TEXT_NODE : left.nodeType; - const rightType = right.nodeType === Node.CDATA_SECTION_NODE ? Node.TEXT_NODE : right.nodeType; - return leftType === rightType; - } - }; - - - window.WendigoPathFinder = { - cssPath(node) { - if (node.nodeType !== Node.ELEMENT_NODE) - return ''; - const stepsFunction = _cssPathFinderHelpers._cssPathStep.bind(_cssPathFinderHelpers); - const steps = this._generatePathSteps(node, stepsFunction); - return steps.join(' > '); - }, - xPath(node) { - if (node.nodeType === Node.DOCUMENT_NODE) - return '/'; - - const stepsFunction = _xPathFinderHelpers._xPathStep.bind(_xPathFinderHelpers); - const steps = this._generatePathSteps(node, stepsFunction); - return (steps.length && steps[0].optimized ? '' : '/') + steps.join('/'); - }, - _generatePathSteps(node, stepFunction) { - const steps = []; - let contextNode = node; - while (contextNode) { - const step = stepFunction(contextNode, contextNode === node); - if (!step) - break; // Error - bail out early. - steps.push(step); - if (step.optimized) - break; - contextNode = contextNode.parentNode; - } - - steps.reverse(); - return steps; - } - }; -} - -/* eslint-enable no-continue, complexity */ diff --git a/injection_scripts/selector_finder.ts b/injection_scripts/selector_finder.ts new file mode 100644 index 00000000..ad53b594 --- /dev/null +++ b/injection_scripts/selector_finder.ts @@ -0,0 +1,265 @@ +// Based on https://github.com/ChromeDevTools/devtools-frontend/blob/master/front_end/elements/DOMPath.js + +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found at https://github.com/ChromeDevTools/devtools-frontend/blob/master/LICENSE + +// Minor refactor and modifications made by @angrykoala for wendigo + +export default function SelectorFinderLoader(): void { + + function nodeIsElement(node: Node): node is Element { + return node.nodeType === Node.ELEMENT_NODE; + } + + if (!(window as any).WendigoPathFinder) { + class Step { + public value: string; + public optimized: boolean; + + constructor(value: string, optimized?: boolean) { + this.value = value; + this.optimized = optimized || false; + } + + public toString(): string { + return this.value; + } + } + + const _cssPathFinderHelpers = { + _stepPreprocess(node: Node): Step | null | undefined { + if (!nodeIsElement(node)) + return null; + + const id = node.getAttribute('id'); + if (id) + return new Step(this.idSelector(id), true); + const nodeNameLower = node.nodeName.toLowerCase(); + if (nodeNameLower === 'body' || nodeNameLower === 'head' || nodeNameLower === 'html') + return new Step(node.localName, true); + const nodeName = node.localName; + + const parent = node.parentNode; + if (!parent || parent.nodeType === Node.DOCUMENT_NODE) + return new Step(nodeName, true); + + return undefined; + }, + + _cssPathStep(node: Element, isTargetNode: boolean): Step | null { + const value = this._stepPreprocess(node); + if (value !== undefined) return value; + + const parent = node.parentNode; + const nodeName = node.localName; + + const prefixedOwnClassNamesArray = this.prefixedElementClassNames(node); + let needsClassNames = false; + let needsNthChild = false; + let ownIndex = -1; + let elementIndex = -1; + const siblings = (parent as Node & ParentNode).children; + for (let i = 0; + (ownIndex === -1 || !needsNthChild) && i < siblings.length; ++i) { + const sibling = siblings[i]; + if (sibling.nodeType !== Node.ELEMENT_NODE) + continue; + elementIndex += 1; + if (sibling === node) { + ownIndex = elementIndex; + continue; + } + if (needsNthChild) + continue; + if (sibling.localName !== nodeName) + continue; + + needsClassNames = true; + const ownClassNames = new Set(prefixedOwnClassNamesArray); + if (!ownClassNames.size) { + needsNthChild = true; + continue; + } + const siblingClassNamesArray = this.prefixedElementClassNames(sibling); + for (let j = 0; j < siblingClassNamesArray.length; ++j) { + const siblingClass = siblingClassNamesArray[j]; + if (!ownClassNames.has(siblingClass)) + continue; + ownClassNames.delete(siblingClass); + if (!ownClassNames.size) { + needsNthChild = true; + break; + } + } + } + + let result = nodeName; + if (isTargetNode && nodeName.toLowerCase() === 'input' && node.getAttribute('type') && !node.getAttribute('id') && + !node.getAttribute('class')) + result += `[type="${node.getAttribute('type')}"]`; + if (needsNthChild) { + result += `:nth-child(${ownIndex + 1})`; + } else if (needsClassNames) { + for (const prefixedName of prefixedOwnClassNamesArray) + result += `.${this.escapeIdentifierIfNeeded(prefixedName.substr(1))}`; + } + + return new Step(result, false); + }, + prefixedElementClassNames(node: Element): Array { + const classAttribute = node.getAttribute('class'); + if (!classAttribute) + return []; + + return classAttribute.split(/\s+/g).filter(Boolean).map((name) => { + // The prefix is required to store "__proto__" in a object-based map. + return `$${name}`; + }); + }, + idSelector(id: string): string { + return `#${this.escapeIdentifierIfNeeded(id)}`; + }, + escapeIdentifierIfNeeded(ident: string): string { + if (this.isCSSIdentifier(ident)) + return ident; + const shouldEscapeFirst = /^(?:[0-9]|-[0-9-]?)/.test(ident); + const lastIndex = ident.length - 1; + return ident.replace(/./g, (c, i) => { + return ((shouldEscapeFirst && i === 0) || !this.isCSSIdentChar(c)) ? this.escapeAsciiChar(c, i === lastIndex) : c; + }); + }, + escapeAsciiChar(c: string, isLast: boolean): string { + return `\\${this.toHexByte(c)}${isLast ? '' : ' '}`; + }, + toHexByte(c: string): string { + let hexByte = c.charCodeAt(0).toString(16); + if (hexByte.length === 1) + hexByte = `0${hexByte}`; + return hexByte; + }, + isCSSIdentChar(c: string): boolean { + if (/[a-zA-Z0-9_-]/.test(c)) + return true; + return c.charCodeAt(0) >= 0xA0; + }, + isCSSIdentifier(value: string): boolean { + // Double hyphen prefixes are not allowed by specification, but many sites use it. + return /^-{0,2}[a-zA-Z_][a-zA-Z0-9_-]*$/.test(value); + } + }; + const _xPathFinderHelpers = { + _xPathStep(node: Node): Step | null { + let ownValue; + const ownIndex = this._xPathIndex(node); + if (ownIndex === -1) + return null; // Error. + + switch (node.nodeType) { + case Node.ELEMENT_NODE: + if ((node as Element).getAttribute('id')) + return new Step(`//*[@id="${(node as Element).getAttribute('id')}"]`, true); + ownValue = (node as Element).localName; + break; + case Node.ATTRIBUTE_NODE: + ownValue = `@${node.nodeName}`; + break; + case Node.TEXT_NODE: + case Node.CDATA_SECTION_NODE: + ownValue = 'text()'; + break; + case Node.PROCESSING_INSTRUCTION_NODE: + ownValue = 'processing-instruction()'; + break; + case Node.COMMENT_NODE: + ownValue = 'comment()'; + break; + case Node.DOCUMENT_NODE: + ownValue = ''; + break; + default: + ownValue = ''; + break; + } + + if (ownIndex > 0) + ownValue += `[${ownIndex}]`; + + return new Step(ownValue, node.nodeType === Node.DOCUMENT_NODE); + }, + _xPathIndex(node: Node): number { + const siblings = node.parentNode ? node.parentNode.children : null; + if (!siblings) + return 0; // Root node - no siblings. + let hasSameNamedElements = false; + for (let i = 0; i < siblings.length; ++i) { + if (this.areNodesSimilar(node, siblings[i]) && siblings[i] !== node) { + hasSameNamedElements = true; + break; + } + } + if (!hasSameNamedElements) + return 0; + let ownIndex = 1; // XPath indices start with 1. + for (let i = 0; i < siblings.length; ++i) { + if (this.areNodesSimilar(node, siblings[i])) { + if (siblings[i] === node) + return ownIndex; + ownIndex++; + } + } + return -1; // An error occurred: |node| not found in parent's children. + }, + areNodesSimilar(left: Node, right: Node): boolean { + // Returns -1 in case of error, 0 if no siblings matching the same expression, otherwise. + if (left === right) + return true; + + if (nodeIsElement(left) && nodeIsElement(right)) + return left.localName === right.localName; + + if (left.nodeType === right.nodeType) + return true; + + // XPath treats CDATA as text nodes. + const leftType = left.nodeType === Node.CDATA_SECTION_NODE ? Node.TEXT_NODE : left.nodeType; + const rightType = right.nodeType === Node.CDATA_SECTION_NODE ? Node.TEXT_NODE : right.nodeType; + return leftType === rightType; + } + }; + + (window as any).WendigoPathFinder = { + cssPath(node: Node): string { + if (!nodeIsElement(node)) + return ''; + const stepsFunction = _cssPathFinderHelpers._cssPathStep.bind(_cssPathFinderHelpers); + const steps = this._generatePathSteps(node, stepsFunction); + return steps.join(' > '); + }, + xPath(node: Node): string { + if (node.nodeType === Node.DOCUMENT_NODE) + return '/'; + + const stepsFunction = _xPathFinderHelpers._xPathStep.bind(_xPathFinderHelpers); + const steps = this._generatePathSteps(node, stepsFunction); + return (steps.length && steps[0].optimized ? '' : '/') + steps.join('/'); + }, + _generatePathSteps(node: Node, stepFunction: (node: Node, isTarget: boolean) => Step): Array { + const steps = []; + let contextNode = node; + while (contextNode) { + const step = stepFunction(contextNode, contextNode === node); + if (!step) + break; // Error - bail out early. + steps.push(step); + if (step.optimized) + break; + contextNode = contextNode.parentNode as Node; + } + + steps.reverse(); + return steps; + } + }; + } +} diff --git a/injection_scripts/selector_query.js b/injection_scripts/selector_query.js deleted file mode 100644 index 5635edc5..00000000 --- a/injection_scripts/selector_query.js +++ /dev/null @@ -1,94 +0,0 @@ -"use strict"; - -if (!window.WendigoQuery) { - window.WendigoQuery = { - selectorTypes: {// Warning: Same as selector type - css: "css", - xpath: "xpath", - domElement: "domElement" - }, - query(selector) { - const type = this._parseSelectorType(selector); - switch (type) { - case this.selectorTypes.css: - return this.queryCss(selector); - case this.selectorTypes.xpath: - return this.queryXPath(selector); - case this.selectorTypes.domElement: - return this.queryDomElement(selector); - default: - throw new Error(`Query Error: ${selector} with type ${type}`); - } - }, - queryAll(selector) { - const type = this._parseSelectorType(selector); - - switch (type) { - case this.selectorTypes.css: - return this.queryCssAll(selector); - case this.selectorTypes.xpath: - return this.queryXPathAll(selector); - case this.selectorTypes.domElement: - return this.queryDomElementAll(selector); - default: - throw new Error(`QueryAll Error: ${selector} with type ${type}`); - } - }, - queryCss(cssSelector) { - return document.querySelector(cssSelector); - }, - queryCssAll(cssSelector) { - return Array.from(document.querySelectorAll(cssSelector)); - }, - queryDomElement(element) { - if (Array.isArray(element)) return element[0]; - else return element; - }, - queryDomElementAll(elements) { - if (Array.isArray(elements)) return elements; - else return [elements]; - }, - queryXPath(xPath) { - const xPathResult = document.evaluate(xPath, document, null, XPathResult.UNORDERED_NODE_ITERATOR_TYPE, null); - const result = xPathResult.iterateNext(); - if (result.nodeType !== 1) return null; - return result; - }, - queryXPathAll(xPath) { - const xPathResult = document.evaluate(xPath, document, null, XPathResult.UNORDERED_NODE_ITERATOR_TYPE, null); - const result = []; - let r = xPathResult.iterateNext(); - while (r !== null) { - if (r.nodeType === 1) // Not an element - result.push(r); - r = xPathResult.iterateNext(); - } - return result; - }, - - _parseSelectorType(selector) { - if (typeof(selector) === "string") { - if (selector.length === 0) return null; - if (this._isXPathQuery(selector)) return this.selectorTypes.xpath; - else return this.selectorTypes.css; - } else if (typeof(selector) === "object") { - return this.selectorTypes.domElement; - } else return null; - }, - - // NOTE: Duplicate of utils.isXPathQuery - _isXPathQuery(s) { - if (s[0] === '/') return true; - if (/^.\./.test(s)) return true; - const axisSplit = s.split("::"); - if (axisSplit.length > 1) { - const validAxis = ["ancestor", "ancestor-or-self", "attribute", "child", "descendant", "descendant-or-self", - "following", "following-sibling", "namespace", "parent", "preceding", "preceding-sibling", "self" - ]; - const axe = axisSplit[0]; - if (validAxis.indexOf(axe) !== -1) return true; - } - return false; - } - }; -} diff --git a/injection_scripts/selector_query.ts b/injection_scripts/selector_query.ts new file mode 100644 index 00000000..d6908453 --- /dev/null +++ b/injection_scripts/selector_query.ts @@ -0,0 +1,94 @@ +export default function SelectorQueryLoader(): void { + if (!(window as any).WendigoQuery) { + (window as any).WendigoQuery = { + selectorTypes: {// Warning: Same as selector type + css: "css", + xpath: "xpath", + domElement: "domElement" + }, + query(selector: string): Element | null { + const type = this._parseSelectorType(selector); + switch (type) { + case this.selectorTypes.css: + return this.queryCss(selector); + case this.selectorTypes.xpath: + return this.queryXPath(selector); + case this.selectorTypes.domElement: + return this.queryDomElement(selector); + default: + throw new Error(`Query Error: ${selector} with type ${type}`); + } + }, + queryAll(selector: string): Array { + const type = this._parseSelectorType(selector); + + switch (type) { + case this.selectorTypes.css: + return this.queryCssAll(selector); + case this.selectorTypes.xpath: + return this.queryXPathAll(selector); + case this.selectorTypes.domElement: + return this.queryDomElementAll(selector); + default: + throw new Error(`QueryAll Error: ${selector} with type ${type}`); + } + }, + queryCss(cssSelector: string): Element | null { + return document.querySelector(cssSelector); + }, + queryCssAll(cssSelector: string): Array { + return Array.from(document.querySelectorAll(cssSelector)); + }, + queryDomElement(element: Element | Array): Element { + if (Array.isArray(element)) return element[0]; + else return element; + }, + queryDomElementAll(elements: Array): Array { + if (Array.isArray(elements)) return elements; + else return [elements]; + }, + queryXPath(xPath: string): Element | null { + const xPathResult = document.evaluate(xPath, document, null, XPathResult.UNORDERED_NODE_ITERATOR_TYPE, null); + const result = xPathResult.iterateNext(); + if (!result || result.nodeType !== 1) return null; + return result as Element; // TODO: check if this is ok + }, + queryXPathAll(xPath: string): Array { + const xPathResult = document.evaluate(xPath, document, null, XPathResult.UNORDERED_NODE_ITERATOR_TYPE, null); + const result = []; + let r = xPathResult.iterateNext(); + while (r !== null) { + if (r.nodeType === 1) // Not an element + result.push(r as Element); + r = xPathResult.iterateNext(); + } + return result; + }, + + _parseSelectorType(selector: string): string | null { + if (typeof (selector) === "string") { + if (selector.length === 0) return null; + if (this._isXPathQuery(selector)) return this.selectorTypes.xpath; + else return this.selectorTypes.css; + } else if (typeof (selector) === "object") { + return this.selectorTypes.domElement; + } else return null; + }, + + // NOTE: Duplicate of utils.isXPathQuery + _isXPathQuery(s: string): boolean { + if (s[0] === '/') return true; + if (/^.\./.test(s)) return true; + const axisSplit = s.split("::"); + if (axisSplit.length > 1) { + const validAxis = ["ancestor", "ancestor-or-self", "attribute", "child", "descendant", "descendant-or-self", + "following", "following-sibling", "namespace", "parent", "preceding", "preceding-sibling", "self" + ]; + const axe = axisSplit[0]; + if (validAxis.indexOf(axe) !== -1) return true; + } + return false; + } + }; + } +} diff --git a/injection_scripts/wendigo_utils.js b/injection_scripts/wendigo_utils.js deleted file mode 100644 index b0913de8..00000000 --- a/injection_scripts/wendigo_utils.js +++ /dev/null @@ -1,77 +0,0 @@ -/* global WendigoQuery, WendigoPathFinder */ -"use strict"; - -if (!window.WendigoUtils) { - const _origDate = Date; - - window.WendigoUtils = { - isVisible(element) { - if (!element) return false; - if (element === document) return true; // Top element, always visible - const style = window.getComputedStyle(element); - if (style.display === 'none' || - style.visibility === 'hidden' || - style.opacity === '0') return false; - return this.isVisible(element.parentNode); - }, - queryElement(selector) { - return WendigoQuery.query(selector); - }, - queryAll(selector) { - return WendigoQuery.queryAll(selector); - }, - xPathQuery(xPath) { - return WendigoQuery.queryXPathAll(xPath); - }, - getStyles(element) { - const rawStyles = getComputedStyle(element); - const result = {}; - for (let i = 0; i < rawStyles.length; i++) { - const name = rawStyles[i]; - result[name] = rawStyles.getPropertyValue(name); - } - return result; - }, - mockDate(timestamp, freeze) { - let baseTimestamp = 0; - if (!freeze) { - baseTimestamp = new _origDate().getTime(); - } - function getCurrentTimestamp() { - if (!freeze) { - const currentTimestamp = new _origDate().getTime(); - const timeDiff = currentTimestamp - baseTimestamp; - return timestamp + timeDiff; - } else return timestamp; - } - - // Based on https://github.com/capaj/proxy-date - window.Date = new Proxy(_origDate, { - construct(Target, args) { - if (args.length === 0) { - return new Target(getCurrentTimestamp()); - } - return new Target(...args); - }, - get(Target, prop) { - if (prop === 'now') { - return () => getCurrentTimestamp(); - } - return Reflect.get(...arguments); - }, - apply(Target) { - return new Target(getCurrentTimestamp()).toString(); - } - }); - }, - clearDateMock() { - window.Date = _origDate; - }, - findCssPath(node) { - return WendigoPathFinder.cssPath(node); - }, - findXPath(node) { - return WendigoPathFinder.xPath(node); - } - }; -} diff --git a/injection_scripts/wendigo_utils.ts b/injection_scripts/wendigo_utils.ts new file mode 100644 index 00000000..64985147 --- /dev/null +++ b/injection_scripts/wendigo_utils.ts @@ -0,0 +1,83 @@ +declare const WendigoQuery: any; +declare const WendigoPathFinder: any; + +export default function WendigoUtilsLoader(): void { + if (!(window as any).WendigoUtils) { + const _origDate = Date; + + (window as any).WendigoUtils = { + isVisible(element: Element | Document): boolean { + if (!element) return false; + if (element === document) return true; // Top element, always visible + else { + const style = window.getComputedStyle(element as Element); + if (style.display === 'none' || + style.visibility === 'hidden' || + style.opacity === '0') return false; + return this.isVisible(element.parentNode); + } + }, + queryElement(selector: any): Element { + return WendigoQuery.query(selector); + }, + queryAll(selector: any): Array { + return WendigoQuery.queryAll(selector); + }, + xPathQuery(xPath: any): Array { + return WendigoQuery.queryXPathAll(xPath); + }, + getStyles(element: Element): { [s: string]: string } { + const rawStyles = getComputedStyle(element); + const result: { [s: string]: string } = {}; + for (let i = 0; i < rawStyles.length; i++) { + const name = rawStyles[i]; + result[name] = rawStyles.getPropertyValue(name); + } + return result; + }, + mockDate(timestamp: number, freeze: boolean): void { + let baseTimestamp = 0; + if (!freeze) { + baseTimestamp = new _origDate().getTime(); + } + function getCurrentTimestamp(): number { + if (!freeze) { + const currentTimestamp = new _origDate().getTime(); + const timeDiff = currentTimestamp - baseTimestamp; + return timestamp + timeDiff; + } else return timestamp; + } + + // Based on https://github.com/capaj/proxy-date + (window as any).Date = new Proxy(_origDate, { + construct(target: typeof Date, args: Array): Date { + if (args.length === 0) { + return new target(getCurrentTimestamp()); + } + // @ts-ignore + return new target(...args); + }, + get(_target: typeof Date, prop): () => number { + if (prop === 'now') { + return () => getCurrentTimestamp(); + } + // @ts-ignore + return Reflect.get(...arguments); + }, + apply(target: typeof Date): string { + return new target(getCurrentTimestamp()).toString(); + } + }); + }, + clearDateMock(): void { + (window as any).Date = _origDate; + }, + findCssPath(node: Element | Document): string { + return WendigoPathFinder.cssPath(node); + }, + findXPath(node: Element | Document): string { + return WendigoPathFinder.xPath(node); + } + }; + } +} diff --git a/lib/browser/browser_core.ts b/lib/browser/browser_core.ts index c55ecfee..46a84bbf 100644 --- a/lib/browser/browser_core.ts +++ b/lib/browser/browser_core.ts @@ -2,7 +2,6 @@ import path from 'path'; import querystring from 'querystring'; import { stringifyLogText } from '../puppeteer_wrapper/puppeteer_utils'; -import WendigoConfig from '../../config'; import DomElement from '../models/dom_element'; import { FatalError, InjectScriptError } from '../errors'; import { FinalBrowserSettings, OpenSettings } from '../types'; @@ -12,8 +11,9 @@ import FailIfNotLoaded from '../decorators/fail_if_not_loaded'; import PuppeteerContext from '../puppeteer_wrapper/puppeteer_context'; import OverrideError from '../decorators/override_error'; -const injectionScriptsPath = WendigoConfig.injectionScripts.path; -const injectionScripts = WendigoConfig.injectionScripts.files; +import WendigoUtilsLoader from '../../injection_scripts/selector_query'; +import SelectorQueryLoader from '../../injection_scripts/wendigo_utils'; +import SelectorFinderLoader from '../../injection_scripts/selector_finder'; async function pageLog(log?: ConsoleMessage): Promise { if (log) { @@ -217,7 +217,10 @@ export default abstract class BrowserCore { path: scriptPath }); } catch (err) { - return Promise.reject(new InjectScriptError("addScript", err)); + if (err.message === "Evaluation failed: Event") { + const cspWarning = "This may be caused by the page Content Security Policy. Make sure the option bypassCSP is set to true in Wendigo."; + throw new InjectScriptError("addScript", `Error injecting scripts. ${cspWarning}`); // CSP error + } else throw new InjectScriptError("addScript", err); } } @@ -244,27 +247,19 @@ export default abstract class BrowserCore { } protected async _afterPageLoad(): Promise { - try { - const content = await this._page.content(); - this._originalHtml = content; - await this._addJsScripts(); - } catch (err) { - if (err.message === "Evaluation failed: Event") { - const cspWarning = "This may be caused by the page Content Security Policy. Make sure the option bypassCSP is set to true in Wendigo."; - throw new InjectScriptError("_afterPageLoad", `Error injecting scripts. ${cspWarning}`); // CSP error - } - } + const content = await this._page.content(); + this._originalHtml = content; + await this._addJsScripts(); this._loaded = true; await this._callComponentsMethod("_afterOpen"); } private async _addJsScripts(): Promise { - const promises = injectionScripts.map((s) => { - return this._page.addScriptTag({ // Not using wrapper as this is before loaded is true - path: path.join(injectionScriptsPath, s) - }); - }); - await Promise.all(promises); + await Promise.all([ + this._page.evaluateHandle(WendigoUtilsLoader), + this._page.evaluateHandle(SelectorQueryLoader), + this._page.evaluateHandle(SelectorFinderLoader) + ]); } private _setupEvaluateArguments(args: Array): Array { diff --git a/tests/browser/open.test.js b/tests/browser/open.test.js index f70a7313..b6c217f7 100644 --- a/tests/browser/open.test.js +++ b/tests/browser/open.test.js @@ -1,6 +1,7 @@ "use strict"; const assert = require('assert'); +const path = require('path'); const Wendigo = require('../..'); const utils = require('../test_utils'); const configUrls = require('../config.json').urls; @@ -53,13 +54,32 @@ describe("Open", function() { assert.strictEqual(browser2._originalHtml, undefined); }); - it("Open Fails CSP", async() => { + it("Open Does Not Fail CSP", async() => { const browser2 = await Wendigo.createBrowser({ bypassCSP: false }); + await browser2.open(configUrls.index); + await browser2.close(); + }); + + it("Add JS Script Bypass CSP", async() => { + const browser2 = await Wendigo.createBrowser({ + bypassCSP: true + }); + await browser2.open(configUrls.index); + await browser2.addScript(path.join(__dirname, "..", "dummy_server/static/worker.js")); + await browser2.close(); + }); + + it("Add JS Script Fail CSP", async() => { + const browser2 = await Wendigo.createBrowser({ + bypassCSP: false + }); + await browser2.open(configUrls.index); await utils.assertThrowsAsync(async() => { - await browser2.open(configUrls.index); - }, `InjectScriptError: [open] Error injecting scripts. This may be caused by the page Content Security Policy. Make sure the option bypassCSP is set to true in Wendigo.`); + await browser2.addScript(path.join(__dirname, "..", "dummy_server/static/worker.js")); + }, `InjectScriptError: [addScript] Error injecting scripts. This may be caused by the page Content Security Policy. Make sure the option bypassCSP is set to true in Wendigo.`); // eslint-disable-line max-len + await browser2.close(); }); From 63a0d3743cb272be2a070c2f231587094fdc15bf Mon Sep 17 00:00:00 2001 From: angrykoala Date: Tue, 6 Aug 2019 20:24:30 +0200 Subject: [PATCH 6/7] removed reload on new tab, close #402 --- CHANGELOG.md | 1 + README.md | 2 +- lib/browser/browser_core.ts | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ff319d8..6bec0503 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * DefaultTimeout option on create browser * Injection scripts are now added through evaluate, so are not checked by CSP * Static config file removed +* Opening new tabs will no longer reload the page 2.5.1 / 2019-07-27 ================== diff --git a/README.md b/README.md index 69c90bc7..15a39224 100644 --- a/README.md +++ b/README.md @@ -300,7 +300,7 @@ await browser.pages(); // length is 2 await browser.selectPage(1); // goes to newly opened tab ``` -> Due to some limitation, in order for Wendigo to work properly, changing page with `selectPage` will cause the given page to reload. +> CSP bypass will not be enabled in the newly opened tabs. If you rely on it, it may be necessary to reload the tab after it has been opened with `browser.refresh` **closePage(index: number)** Closes the page with given index, if the closed page is the current active page, it will change to the new page with index 0 (reloading it in the process). If no more pages exists, the browser will close with `browser.close()` automatically. diff --git a/lib/browser/browser_core.ts b/lib/browser/browser_core.ts index 46a84bbf..12bc6771 100644 --- a/lib/browser/browser_core.ts +++ b/lib/browser/browser_core.ts @@ -172,7 +172,7 @@ export default abstract class BrowserCore { if (!page) throw new FatalError("selectPage", `Invalid page index "${index}".`); this._page = page; // TODO: Avoid reload - await this.page.reload(); // Required to enable bypassCSP + // await this.page.reload(); // Required to enable bypassCSP await this._beforeOpen(this._openSettings); await this._afterPageLoad(); } From d04016aa8be14996a577d1b249a1b7a0091913ae Mon Sep 17 00:00:00 2001 From: angrykoala Date: Tue, 6 Aug 2019 21:14:39 +0200 Subject: [PATCH 7/7] 2.6.0 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6bec0503..7c006d9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -2.6.0 / ####-##-## +2.6.0 / 2019-08-06 ================== * Browser.text will return newlines on