Skip to content

Commit

Permalink
Merge pull request #147 from Seneca-CDOT/feat/api-injection
Browse files Browse the repository at this point in the history
feat: add injector for missing jsdom apis
  • Loading branch information
poftadeh authored Jan 29, 2020
2 parents 387471c + c9084f0 commit a158ee8
Show file tree
Hide file tree
Showing 2 changed files with 120 additions and 12 deletions.
54 changes: 42 additions & 12 deletions src/Browser/BrowserConfig.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { ResourceLoader } from 'jsdom';
import { Pluma } from '../Types/types';
import { CookieJar, MemoryCookieStore } from '../jsdom_extensions/tough-cookie/lib/cookie';
import {
CookieJar,
MemoryCookieStore,
} from '../jsdom_extensions/tough-cookie/lib/cookie';
import { InvalidArgument } from '../Error/errors';

import * as Utils from '../utils/utils';
Expand All @@ -11,22 +14,28 @@ import * as Utils from '../utils/utils';
export class BrowserConfig {
/** defines the context under which scripts can run, if at all */
runScripts: Pluma.RunScripts | null;

/** defines whether self-signed or insecure SSL certificates should be trusted */
strictSSL: boolean = true;
strictSSL = true;

/** defines the type of behaviour when a user prompt is encountered see [W3C unhandledPromptBehaviour](https://w3c.github.io/webdriver/#dfn-unhandled-prompt-behavior) */
readonly unhandledPromptBehaviour: Pluma.UnhandledPromptBehaviour = 'dismiss';

/** the jsdom [resource loader](https://github.com/jsdom/jsdom#loading-subresources)
* allows a more comprehensive customization of jsdom resource-loading behaviour
*/
readonly resourceLoader: ResourceLoader;

/** allows modification of the jsdom environment after the Window and Document
* objects have been created but before any HTML is parsed
*/
*/
readonly beforeParse: Pluma.BeforeParse;

/** a modified tough-cookie cookie jar. Allows '.local' domains to be used for testing purposes
* by setting the rejectPublicSuffixes option
*/
readonly jar;

/** tough-cookie cookieJar option. Prevents public suffixes from being rejected by tough cookie */
readonly rejectPublicSuffixes: boolean;

Expand All @@ -37,7 +46,7 @@ export class BrowserConfig {
if (!Utils.isBrowserOptions(options))
throw new Error('Invalid jsdom options');

Object.keys(options).forEach((option) => {
Object.keys(options).forEach(option => {
if (option === 'strictSSL' && typeof options[option] !== 'boolean')
throw new InvalidArgument();
else if (
Expand All @@ -47,7 +56,7 @@ export class BrowserConfig {
throw new InvalidArgument();
else if (option === 'runScripts')
this[option] = options[option] ? 'dangerously' : undefined;
else if (option === 'strictSSL') this[option] = !options[option]
else if (option === 'strictSSL') this[option] = !options[option];
else this[option] = options[option];
});

Expand All @@ -65,19 +74,19 @@ export class BrowserConfig {

switch (options.unhandledPromptBehaviour) {
case 'accept':
this.beforeParse = BrowserConfig.beforeParseFactory(() => true);
this.beforeParse = this.beforeParseFactory(() => true);
break;
case 'dismiss':
this.beforeParse = BrowserConfig.beforeParseFactory(() => false);
this.beforeParse = this.beforeParseFactory(() => false);
break;
case 'dismiss and notify':
this.beforeParse = BrowserConfig.beforeParseFactory((message) => {
this.beforeParse = this.beforeParseFactory(message => {
console.log(message);
return false;
});
break;
case 'accept and notify':
this.beforeParse = BrowserConfig.beforeParseFactory((message) => {
this.beforeParse = this.beforeParseFactory(message => {
console.log(message);
return true;
});
Expand All @@ -92,11 +101,32 @@ export class BrowserConfig {
/**
* Accepts a [[Pluma.UserPrompt]] object
* to define the window.alert, window.prompt and window.confirm methods */
static beforeParseFactory(func: Pluma.UserPrompt) {
return (window) => {
['confirm', 'alert', 'prompt'].forEach((method) => {
private beforeParseFactory = (func: Pluma.UserPrompt) => {
return window => {
['confirm', 'alert', 'prompt'].forEach(method => {
window[method] = func;
});

this.injectAPIs(window);
};
};

/**
* Injects missing APIs into jsdom for better compatibility.
*/
private injectAPIs(window): void {
window.HTMLElement.prototype.scrollIntoView = () => undefined;

window.performance.timing = {
navigationStart: window.performance.timeOrigin,
};

window.SVGRectElement = Object.create(window.SVGElement);
window.SVGRectElement.prototype.getBBox = () => ({
x: 1,
y: 1,
width: 1,
height: 1,
});
}
}
78 changes: 78 additions & 0 deletions test/jest/e2e/inject-api.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
const request = require('supertest');
const nock = require('nock');

const { app } = require('../../../build/app');
const { createSession } = require('./helpers');

describe('Injected APIs', () => {
let sessionId;

beforeAll(async () => {
nock(/plumadriver\.com/)
.get('/')
.delay(100)
.reply(
200,
`<html>
<head>
<title>Test Page</title>
</head>
<body>
<h1>Test</h1>
<svg width="400" height="110">
<rect width="200" height="50" style="fill:rgb(0,0,255); stroke-width:2; stroke:rgb(0,0,0)" />
</svg>
</body>
</html>`,
);

sessionId = await createSession(request, app);

await request(app)
.post(`/session/${sessionId}/url`)
.send({
url: 'http://plumadriver.com',
})
.expect(200);
});

it('should not report error on HTMLElement.scrollIntoView', async () => {
await request(app)
.post(`/session/${sessionId}/execute/sync`)
.send({
script: 'document.querySelector("h1").scrollIntoView()',
args: [],
})
.expect(200);
});

it('should return a number value from performance.timing.navigationStart', async () => {
const {
body: { value },
} = await request(app)
.post(`/session/${sessionId}/execute/sync`)
.send({ script: 'return performance.timing.navigationStart', args: [] })
.expect(200);

expect(value).toEqual(expect.any(Number));
});

it('should have the proper return value for SVGRectElement.getBBox()', async () => {
const {
body: { value },
} = await request(app)
.post(`/session/${sessionId}/execute/sync`)
.send({
script: 'return document.querySelector("rect").getBBox()',
args: [],
})
.expect(200);

expect(value).toMatchObject({
x: expect.any(Number),
y: expect.any(Number),
width: expect.any(Number),
height: expect.any(Number),
});
});
});

0 comments on commit a158ee8

Please sign in to comment.