Puppeteer: staging commit.

This commit is contained in:
Andrey Lushnikov 2017-05-11 00:06:41 -07:00
parent ebac211411
commit 2cda8c18d1
180 changed files with 19038 additions and 2 deletions

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
/node_modules/
/.local-chromium/
/.dev_profile*
.DS_Store
*.swp
*.pyc

23
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,23 @@
# How to Contribute
We'd love to accept your patches and contributions to this project. There are
just a few small guidelines you need to follow.
## Contributor License Agreement
Contributions to this project must be accompanied by a Contributor License
Agreement. You (or your employer) retain the copyright to your contribution,
this simply gives us permission to use and redistribute your contributions as
part of the project. Head over to <https://cla.developers.google.com/> to see
your current agreements on file or to sign a new one.
You generally only need to submit a CLA once, so if you've already submitted one
(even if it was for a different project), you probably don't need to do it
again.
## Code reviews
All submissions, including submissions by project members, require review. We
use GitHub pull requests for this purpose. Consult
[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
information on using pull requests.

View File

@ -1,2 +1,36 @@
# puppeteer
Headless Chrome Node API
### Status
Test results on Mac OS X in headless mode:
```
111 passed
20 failed as expected
1 skipped
49 unsupported
```
### Installing
```bash
npm i
npm link # this adds puppeteer to $PATH
```
### Run
```bash
# run phantomjs script
puppeteer third_party/phantomjs/examples/colorwheel.js
# run 'headful'
puppeteer --no-headless third_party/phantomjs/examples/colorwheel.js
# run puppeteer example
node examples/screenshot.js
```
### Tests
Run phantom.js tests using puppeteer:
```
./third_party/phantomjs/test/run-tests.py
```

View File

@ -0,0 +1,41 @@
/**
* Copyright 2017 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var Browser = require('../lib/Browser');
var Downloader = require('../lib/Downloader');
var revision = "464642";
console.log('Downloading custom chromium revision - ' + revision);
Downloader.downloadChromium(revision).then(async () => {
console.log('Done.');
var executablePath = Downloader.executablePath(revision);
var browser1 = new Browser({
remoteDebuggingPort: 9228,
executablePath,
});
var browser2 = new Browser({
remoteDebuggingPort: 9229,
});
var [version1, version2] = await Promise.all([
browser1.version(),
browser2.version()
]);
console.log('browser1: ' + version1);
console.log('browser2: ' + version2);
browser1.close();
browser2.close();
});

27
examples/screenshot.js Normal file
View File

@ -0,0 +1,27 @@
/**
* Copyright 2017 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var Browser = require('./lib/Browser');
var Page = require('./lib/Page');
var fs = require('fs');
var browser = new Browser();
browser.newPage().then(async page => {
await page.navigate('http://example.com');
var screenshotBuffer = await page.screenshot(Page.ScreenshotTypes.PNG);
fs.writeFileSync('example.png', screenshotBuffer);
browser.close();
});

60
index.js Executable file
View File

@ -0,0 +1,60 @@
#!/usr/bin/env node
/**
* Copyright 2017 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var vm = require('vm');
var fs = require('fs');
var path = require('path');
var Browser = require('./lib/Browser');
var argv = require('minimist')(process.argv.slice(2), {
alias: { v: 'version' },
boolean: ['headless'],
default: {'headless': true },
});
if (argv.version) {
console.log('Puppeteer v' + require('./package.json').version);
return;
}
if (argv['ssl-certificates-path']) {
console.error('Flag --ssl-certificates-path is not currently supported.\nMore information at https://github.com/aslushnikov/puppeteer/issues/1');
process.exit(1);
return;
}
var scriptArguments = argv._;
if (!scriptArguments.length) {
console.log('puppeteer [scriptfile]');
return;
}
var scriptPath = path.resolve(process.cwd(), scriptArguments[0]);
if (!fs.existsSync(scriptPath)) {
console.error(`script not found: ${scriptPath}`);
process.exit(1);
return;
}
var browser = new Browser({
remoteDebuggingPort: 9229,
headless: argv.headless,
});
var PhatomJs = require('./phantomjs');
var context = PhatomJs.createContext(browser, scriptPath, argv);
var scriptContent = fs.readFileSync(scriptPath, 'utf8');
vm.runInContext(scriptContent, context);

32
install.js Normal file
View File

@ -0,0 +1,32 @@
var Downloader = require('./lib/Downloader');
var revision = require('./package').puppeteer.chromium_revision;
var fs = require('fs');
var ProgressBar = require('progress');
var executable = Downloader.executablePath(revision);
if (fs.existsSync(executable))
return;
Downloader.downloadChromium(revision, onProgress)
.catch(error => {
console.error('Download failed: ' + error.message);
});
var progressBar = null;
function onProgress(bytesTotal, delta) {
if (!progressBar) {
progressBar = new ProgressBar(`Downloading Chromium - ${toMegabytes(bytesTotal)} [:bar] :percent :etas `, {
complete: '=',
incomplete: ' ',
width: 20,
total: bytesTotal,
});
}
progressBar.tick(delta);
}
function toMegabytes(bytes) {
var mb = bytes / 1024 / 1024;
return (Math.round(mb * 10) / 10) + ' Mb';
}

142
lib/Browser.js Normal file
View File

@ -0,0 +1,142 @@
/**
* Copyright 2017 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var CDP = require('chrome-remote-interface');
var http = require('http');
var path = require('path');
var removeRecursive = require('rimraf').sync;
var Page = require('./Page');
var childProcess = require('child_process');
var Downloader = require('./Downloader');
var CHROME_PROFILE_PATH = path.resolve(__dirname, '..', '.dev_profile');
var browserId = 0;
var DEFAULT_ARGS = [
'--disable-background-timer-throttling',
'--no-first-run',
];
class Browser {
/**
* @param {(!Object|undefined)} options
*/
constructor(options) {
options = options || {};
++browserId;
this._userDataDir = CHROME_PROFILE_PATH + browserId;
this._remoteDebuggingPort = 9229;
if (typeof options.remoteDebuggingPort === 'number')
this._remoteDebuggingPort = options.remoteDebuggingPort;
this._chromeArguments = DEFAULT_ARGS.concat([
`--user-data-dir=${this._userDataDir}`,
`--remote-debugging-port=${this._remoteDebuggingPort}`,
]);
if (typeof options.headless !== 'boolean' || options.headless) {
this._chromeArguments.push(...[
`--headless`,
`--disable-gpu`,
]);
}
if (typeof options.executablePath === 'string') {
this._chromeExecutable = options.executablePath;
} else {
var chromiumRevision = require('../package.json').puppeteer.chromium_revision;
this._chromeExecutable = Downloader.executablePath(chromiumRevision);
}
if (Array.isArray(options.args))
this._chromeArguments.push(...options.args);
this._chromeProcess = null;
this._tabSymbol = Symbol('Browser.TabSymbol');
}
/**
* @return {!Promise<!Page>}
*/
async newPage() {
await this._ensureChromeIsRunning();
if (!this._chromeProcess || this._chromeProcess.killed)
throw new Error('ERROR: this chrome instance is not alive any more!');
var tab = await CDP.New({port: this._remoteDebuggingPort});
var client = await CDP({tab: tab, port: this._remoteDebuggingPort});
var page = await Page.create(this, client);
page[this._tabSymbol] = tab;
return page;
}
/**
* @param {!Page} page
*/
async closePage(page) {
if (!this._chromeProcess || this._chromeProcess.killed)
throw new Error('ERROR: this chrome instance is not running');
var tab = page[this._tabSymbol];
if (!tab)
throw new Error('ERROR: page does not belong to this chrome instance');
await CDP.Close({id: tab.id, port: this._remoteDebuggingPort});
}
/**
* @return {string}
*/
async version() {
await this._ensureChromeIsRunning();
var version = await CDP.Version({port: this._remoteDebuggingPort});
return version.Browser;
}
async _ensureChromeIsRunning() {
if (this._chromeProcess)
return;
this._chromeProcess = childProcess.spawn(this._chromeExecutable, this._chromeArguments, {});
// Cleanup as processes exit.
process.on('exit', () => this._chromeProcess.kill());
this._chromeProcess.on('exit', () => removeRecursive(this._userDataDir));
await waitForChromeResponsive(this._remoteDebuggingPort);
}
close() {
if (!this._chromeProcess)
return;
this._chromeProcess.kill();
}
}
module.exports = Browser;
function waitForChromeResponsive(remoteDebuggingPort) {
var fulfill;
var promise = new Promise(x => fulfill = x);
var options = {
method: 'GET',
host: 'localhost',
port: remoteDebuggingPort,
path: '/json/list'
};
var probeTimeout = 100;
var probeAttempt = 1;
sendRequest();
return promise;
function sendRequest() {
var req = http.request(options, res => {
fulfill()
});
req.on('error', e => setTimeout(sendRequest, probeTimeout));
req.end();
}
}

123
lib/Downloader.js Normal file
View File

@ -0,0 +1,123 @@
/**
* Copyright 2017 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var os = require('os');
var https = require('https');
var fs = require('fs');
var path = require('path');
var extract = require('extract-zip');
var util = require('util');
var CHROMIUM_PATH = path.join(__dirname, '..', '.local-chromium');
var downloadURLs = {
linux: 'https://storage.googleapis.com/chromium-browser-snapshots/Linux_x64/%d/chrome-linux.zip',
darwin: 'https://storage.googleapis.com/chromium-browser-snapshots/Mac/%d/chrome-mac.zip',
win32: 'https://storage.googleapis.com/chromium-browser-snapshots/Win/%d/chrome-win32.zip',
win64: 'https://storage.googleapis.com/chromium-browser-snapshots/Win_x64/%d/chrome-win32.zip',
};
module.exports = {
downloadChromium,
executablePath,
};
/**
* @param {string} revision
* @param {?function(number, number)} progressCallback
* @return {!Promise}
*/
async function downloadChromium(revision, progressCallback) {
var url = null;
var platform = os.platform();
if (platform === 'darwin')
url = downloadURLs.darwin;
else if (platform === 'linux')
url = downloadURLs.linux;
else if (platform === 'win32')
url = os.arch() === 'x64' ? downloadURLs.win64 : downloadURLs.win32;
console.assert(url, `Unsupported platform: ${platform}`);
url = util.format(url, revision);
var zipPath = path.join(CHROMIUM_PATH, `download-${revision}.zip`);
var folderPath = path.join(CHROMIUM_PATH, revision);
if (fs.existsSync(folderPath))
return;
try {
if (!fs.existsSync(CHROMIUM_PATH))
fs.mkdirSync(CHROMIUM_PATH);
await downloadFile(url, zipPath, progressCallback);
await extractZip(zipPath, folderPath);
} finally {
if (fs.existsSync(zipPath))
fs.unlinkSync(zipPath);
}
}
/**
* @return {string}
*/
function executablePath(revision) {
var platform = os.platform();
if (platform === 'darwin')
return path.join(CHROMIUM_PATH, revision, 'chrome-mac', 'Chromium.app', 'Contents', 'MacOS', 'Chromium');
if (platform === 'linux')
return path.join(CHROMIUM_PATH, revision, 'chrome-linux', 'chrome');
if (platform === 'win32')
return path.join(CHROMIUM_PATH, revision, 'chrome-win32', 'chrome.exe');
throw new Error(`Unsupported platform: ${platform}`);
}
/**
* @param {string} url
* @param {string} destinationPath
* @param {?function(number, number)} progressCallback
* @return {!Promise}
*/
function downloadFile(url, destinationPath, progressCallback) {
var fulfill, reject;
var promise = new Promise((x, y) => { fulfill = x; reject = y; });
var request = https.get(url, response => {
if (response.statusCode !== 200) {
var error = new Error(`Download failed: server returned code ${response.statusCode}. URL: ${url}`);
// consume response data to free up memory
response.resume();
reject(error);
return;
}
var file = fs.createWriteStream(destinationPath);
file.on('finish', () => fulfill());
file.on('error', error => reject(error));
response.pipe(file);
var totalBytes = parseInt(response.headers['content-length'], 10);
if (progressCallback)
response.on('data', onData.bind(null, totalBytes));
});
request.on('error', error => reject(error));
return promise;
function onData(totalBytes, chunk) {
progressCallback(totalBytes, chunk.length);
}
}
/**
* @param {string} zipPath
* @param {string} folderPath
* @return {!Promise<?Error>}
*/
function extractZip(zipPath, folderPath) {
return new Promise(fulfill => extract(zipPath, {dir: folderPath}, fulfill));
}

350
lib/Page.js Normal file
View File

@ -0,0 +1,350 @@
/**
* Copyright 2017 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var fs = require('fs');
var EventEmitter = require('events');
var helpers = require('./helpers');
class Page extends EventEmitter {
/**
* @param {!Browser} browser
* @param {!CDP} client
* @return {!Promise<!Page>}
*/
static async create(browser, client) {
await Promise.all([
client.send('Network.enable', {}),
client.send('Page.enable', {}),
client.send('Runtime.enable', {}),
client.send('Security.enable', {}),
]);
var screenDPI = await helpers.evaluate(client, () => window.devicePixelRatio, []);
var page = new Page(browser, client, screenDPI.result.value);
// Initialize default page size.
await page.setSize({width: 400, height: 300});
return page;
}
/**
* @param {!Browser} browser
* @param {!CDP} client
* @param {number} screenDPI
*/
constructor(browser, client, screenDPI) {
super();
this._browser = browser;
this._client = client;
this._screenDPI = screenDPI;
this._extraHeaders = {};
/** @type {!Map<string, function()>} */
this._scriptIdToPageCallback = new Map();
/** @type {!Map<string, string>} */
this._scriptIdToCallbackName = new Map();
client.on('Debugger.paused', event => this._onDebuggerPaused(event));
client.on('Network.responseReceived', event => this.emit(Page.Events.ResponseReceived, event.response));
client.on('Network.loadingFailed', event => this.emit(Page.Events.ResourceLoadingFailed, event));
client.on('Runtime.consoleAPICalled', event => this._onConsoleAPI(event));
client.on('Page.javascriptDialogOpening', dialog => this.emit(Page.Events.DialogOpened, dialog));
client.on('Runtime.exceptionThrown', exception => this._handleException(exception.exceptionDetails));
}
/**
* @param {string} name
* @param {function(?)} callback
*/
async setInPageCallback(name, callback) {
var hasCallback = await this.evaluate(function(name) {
return !!window[name];
}, name);
if (hasCallback)
throw new Error(`Failed to set in-page callback with name ${name}: window['${name}'] already exists!`);
var sourceURL = '__in_page_callback__' + name;
// Ensure debugger is enabled.
await this._client.send('Debugger.enable', {});
var scriptPromise = helpers.waitForScriptWithURL(this._client, sourceURL);
helpers.evaluate(this._client, inPageCallback, [name], false /* awaitPromise */, sourceURL);
var script = await scriptPromise;
if (!script)
throw new Error(`Failed to set in-page callback with name "${name}"`);
this._scriptIdToPageCallback.set(script.scriptId, callback);
this._scriptIdToCallbackName.set(script.scriptId, name);
function inPageCallback(callbackName) {
window[callbackName] = (...args) => {
window[callbackName].__args = args;
window[callbackName].__result = undefined;
debugger;
return window[callbackName].__result;
};
}
}
/**
* @param {string} scriptId
*/
async _handleInPageCallback(scriptId) {
var name = /** @type {string} */ (this._scriptIdToCallbackName.get(scriptId));
var callback = /** @type {function()} */ (this._scriptIdToPageCallback.get(scriptId));
var args = await this.evaluate(callbackName => window[callbackName].__args, name);
var result = callback.apply(null, args);
await this.evaluate(assignResult, name, result);
this._client.send('Debugger.resume');
/**
* @param {string} callbackName
* @param {string} callbackResult
*/
function assignResult(callbackName, callbackResult) {
window[callbackName].__result = callbackResult;
}
}
_onDebuggerPaused(event) {
var location = event.callFrames[0] ? event.callFrames[0].location : null;
if (location && this._scriptIdToPageCallback.has(location.scriptId)) {
this._handleInPageCallback(location.scriptId);
return;
}
this._client.send('Debugger.resume');
}
/**
* @param {!Object} headers
* @return {!Promise}
*/
async setExtraHTTPHeaders(headers) {
this._extraHeaders = {};
// Note: header names are case-insensitive.
for (var key of Object.keys(headers))
this._extraHeaders[key.toLowerCase()] = headers[key];
return this._client.send('Network.setExtraHTTPHeaders', { headers });
}
/**
* @return {!Object}
*/
extraHTTPHeaders() {
return Object.assign({}, this._extraHeaders);
}
/**
* @param {string} userAgent
* @return {!Promise}
*/
async setUserAgentOverride(userAgent) {
this._userAgent = userAgent;
return this._client.send('Network.setUserAgentOverride', { userAgent });
}
/**
* @return {string}
*/
userAgentOverride() {
return this._userAgent;
}
_handleException(exceptionDetails) {
var stack = [];
if (exceptionDetails.stackTrace) {
stack = exceptionDetails.stackTrace.callFrames.map(cf => cf.url);
}
var stackTrace = exceptionDetails.stackTrace;
this.emit(Page.Events.ExceptionThrown, exceptionDetails.exception.description, stack);
}
_onConsoleAPI(event) {
var values = event.args.map(arg => arg.value || arg.description || '');
this.emit(Page.Events.ConsoleMessageAdded, values.join(' '));
}
/**
* @param {boolean} accept
* @param {string} promptText
* @return {!Promise}
*/
async handleDialog(accept, promptText) {
return this._client.send('Page.handleJavaScriptDialog', {accept, promptText});
}
/**
* @return {!Promise<string>}
*/
async url() {
return this.evaluate(function() {
return window.location.href;
});
}
/**
* @param {string} html
* @return {!Promise}
*/
async setContent(html) {
var resourceTree = await this._client.send('Page.getResourceTree', {});
await this._client.send('Page.setDocumentContent', {
frameId: resourceTree.frameTree.frame.id,
html: html
});
}
/**
* @param {string} html
* @return {!Promise<boolean>}
*/
async navigate(url) {
var loadPromise = new Promise(fulfill => this._client.once('Page.loadEventFired', fulfill)).then(() => true);
var interstitialPromise = new Promise(fulfill => this._client.once('Security.certificateError', fulfill)).then(() => false);
var referrer = this._extraHeaders.referer;
// Await for the command to throw exception in case of illegal arguments.
await this._client.send('Page.navigate', {url, referrer});
return await Promise.race([loadPromise, interstitialPromise]);
}
/**
* @param {!{width: number, height: number}} size
* @return {!Promise}
*/
async setSize(size) {
this._size = size;
var width = size.width;
var height = size.height;
var zoom = this._screenDPI;
return Promise.all([
this._client.send('Emulation.setDeviceMetricsOverride', {
width,
height,
deviceScaleFactor: 1,
scale: 1 / zoom,
mobile: false,
fitWindow: false
}),
this._client.send('Emulation.setVisibleSize', {
width: width / zoom,
height: height / zoom,
})
]);
}
/**
* @return {!{width: number, height: number}}
*/
size() {
return this._size;
}
/**
* @param {function()} fun
* @param {!Array<*>} args
* @return {!Promise<(!Object|udndefined)>}
*/
async evaluate(fun, ...args) {
var response = await helpers.evaluate(this._client, fun, args, false /* awaitPromise */);
if (response.exceptionDetails) {
this._handleException(response.exceptionDetails);
return;
}
return response.result.value;
}
/**
* @param {function()} fun
* @param {!Array<*>} args
* @return {!Promise<(!Object|udndefined)>}
*/
async evaluateAsync(fun, ...args) {
var response = await helpers.evaluate(this._client, fun, args, true /* awaitPromise */);
if (response.exceptionDetails) {
this._handleException(response.exceptionDetails);
return;
}
return response.result.value;
}
/**
* @param {!Page.ScreenshotType} screenshotType
* @param {?{x: number, y: number, width: number, height: number}} clipRect
* @return {!Promise<!Buffer>}
*/
async screenshot(screenshotType, clipRect) {
if (clipRect) {
await Promise.all([
this._client.send('Emulation.setVisibleSize', {
width: clipRect.width / this._screenDPI,
height: clipRect.height / this._screenDPI,
}),
this._client.send('Emulation.forceViewport', {
x: clipRect.x / this._screenDPI,
y: clipRect.y / this._screenDPI,
scale: 1,
})
]);
}
var result = await this._client.send('Page.captureScreenshot', {
fromSurface: true,
format: screenshotType,
});
if (clipRect) {
await Promise.all([
this.setSize(this.size()),
this._client.send('Emulation.resetViewport')
]);
}
return new Buffer(result.data, 'base64');
}
/**
* @return {!Promise<string>}
*/
async plainText() {
return this.evaluate(function() {
return document.body.innerText;
});
}
/**
* @return {!Promise<string>}
*/
async title() {
return this.evaluate(function() {
return document.title;
});
}
/**
* @return {!Promise}
*/
async close() {
return this._browser.closePage(this);
}
}
/** @enum {string} */
Page.ScreenshotTypes = {
PNG: "png",
JPG: "jpeg",
};
Page.Events = {
ConsoleMessageAdded: 'Page.Events.ConsoleMessageAdded',
DialogOpened: 'Page.Events.DialogOpened',
ExceptionThrown: 'Page.Events.ExceptionThrown',
ResourceLoadingFailed: 'Page.Events.ResourceLoadingFailed',
ResponseReceived: 'Page.Events.ResponseReceived',
};
module.exports = Page;

68
lib/helpers.js Normal file
View File

@ -0,0 +1,68 @@
/**
* Copyright 2017 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
module.exports = {
/**
* @param {!CDP} client
* @param {string} url
* @return {!Promise<?Object>}
*/
waitForScriptWithURL: function(client, url) {
var fulfill;
var promise = new Promise(x => fulfill = x);
client.on('Debugger.scriptParsed', onScriptParsed);
client.on('Debugger.scriptFailedToParse', onScriptFailedToParse);
return promise;
function onScriptParsed(event) {
if (event.url !== url)
return;
client.removeListener('Debugger.scriptParsed', onScriptParsed);
client.removeListener('Debugger.scriptFailedToParse', onScriptFailedToParse);
fulfill(event);
}
function onScriptFailedToParse(event) {
if (event.url !== url)
return;
client.removeListener('Debugger.scriptParsed', onScriptParsed);
client.removeListener('Debugger.scriptFailedToParse', onScriptFailedToParse);
fulfill(null);
}
},
/**
* @param {!CDP} client
* @param {function()} fun
* @param {!Array<*>} args
* @param {boolean} awaitPromise
* @param {string=} sourceURL
* @return {!Promise<!Object>}
*/
evaluate: function(client, fun, args, awaitPromise, sourceURL) {
var argsString = args.map(x => JSON.stringify(x)).join(',');
var code = `(${fun.toString()})(${argsString})`;
if (awaitPromise)
code = `Promise.resolve(${code})`;
if (sourceURL)
code += `\n//# sourceURL=${sourceURL}`;
return client.send('Runtime.evaluate', {
expression: code,
awaitPromise: awaitPromise,
returnByValue: true
});
},
}

28
package.json Normal file
View File

@ -0,0 +1,28 @@
{
"name": "puppeteer",
"version": "0.0.1",
"description": "",
"main": "index.js",
"scripts": {
"test": "python third_party/phantomjs/test/run-tests.py",
"install": "node install.js"
},
"author": "The Chromium Authors",
"license": "SEE LICENSE IN LICENSE",
"dependencies": {
"chrome-remote-interface": "^0.18.0",
"deasync": "^0.1.9",
"extract-zip": "^1.6.5",
"mime": "^1.3.4",
"minimist": "^1.2.0",
"ncp": "^2.0.0",
"progress": "^2.0.0",
"rimraf": "^2.6.1"
},
"bin": {
"puppeteer": "index.js"
},
"puppeteer": {
"chromium_revision": "468266"
}
}

365
phantomjs/FileSystem.js Normal file
View File

@ -0,0 +1,365 @@
/**
* Copyright 2017 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var path = require('path');
var fs = require('fs');
var deasync = require('deasync');
var removeRecursive = require('rimraf').sync;
var copyRecursive = deasync(require('ncp').ncp);
class FileSystem {
constructor() {
this.separator = path.sep;
}
/**
* @return {string}
*/
get workingDirectory() {
return process.cwd();
}
/**
* @param {string} directoryPath
*/
changeWorkingDirectory(directoryPath) {
try {
process.chdir(directoryPath);
return true;
} catch (e){
return false;
}
}
/**
* @param {string} relativePath
* @return {string}
*/
absolute(relativePath) {
relativePath = path.normalize(relativePath);
if (path.isAbsolute(relativePath))
return relativePath;
return path.resolve(path.join(process.cwd(), relativePath));
}
/**
* @param {string} filePath
* @return {boolean}
*/
exists(filePath) {
return fs.existsSync(filePath);
}
/**
* @param {string} fromPath
* @param {string} toPath
*/
copy(fromPath, toPath) {
var content = fs.readFileSync(fromPath);
fs.writeFileSync(toPath, content);
}
/**
* @param {string} fromPath
* @param {string} toPath
*/
move(fromPath, toPath) {
var content = fs.readFileSync(fromPath);
fs.writeFileSync(toPath, content);
fs.unlinkSync(fromPath);
}
/**
* @param {string} filePath
* @return {number}
*/
size(filePath) {
return fs.statSync(filePath).size;
}
/**
* @param {string} filePath
*/
touch(filePath) {
fs.closeSync(fs.openSync(filePath, 'a'));
}
/**
* @param {string} filePath
*/
remove(filePath) {
fs.unlinkSync(filePath);
}
/**
* @param {string} filePath
*/
lastModified(filePath) {
return fs.statSync(filePath).mtime;
}
/**
* @param {string} dirPath
*/
makeDirectory(dirPath) {
try {
fs.mkdirSync(dirPath);
return true;
} catch (e) {
return false;
}
}
/**
* @param {string} dirPath
*/
removeTree(dirPath) {
removeRecursive(dirPath);
}
/**
* @param {string} fromPath
* @param {string} toPath
*/
copyTree(fromPath, toPath) {
copyRecursive(fromPath, toPath);
}
/**
* @param {string} dirPath
* @return {!Array<string>}
*/
list(dirPath) {
return fs.readdirSync(dirPath);
}
/**
* @param {string} linkPath
* @return {string}
*/
readLink(linkPath) {
return fs.readlinkSync(linkPath);
}
/**
* @param {string} filePath
* @param {Object} data
* @param {string} mode
*/
write(filePath, data, mode) {
var fd = new FileDescriptor(filePath, mode, 'utf8');
fd.write(data);
fd.close();
}
/**
* @param {string} somePath
* @return {boolean}
*/
isAbsolute(somePath) {
return path.isAbsolute(somePath);
}
/**
* @return {string}
*/
read(filePath) {
return fs.readFileSync(filePath, 'utf8');
}
/**
* @param {string} filePath
* @return {boolean}
*/
isFile(filePath) {
return fs.existsSync(filePath) && fs.lstatSync(filePath).isFile();
}
/**
* @param {string} dirPath
* @return {boolean}
*/
isDirectory(dirPath) {
return fs.existsSync(dirPath) && fs.lstatSync(dirPath).isDirectory();
}
/**
* @param {string} filePath
* @return {boolean}
*/
isLink(filePath) {
return fs.existsSync(filePath) && fs.lstatSync(filePath).isSymbolicLink();
}
/**
* @param {string} filePath
* @return {boolean}
*/
isReadable(filePath) {
try {
fs.accessSync(filePath, fs.constants.R_OK);
return true;
} catch (e) {
return false;
}
}
/**
* @param {string} filePath
* @return {boolean}
*/
isWritable(filePath) {
try {
fs.accessSync(filePath, fs.constants.W_OK);
return true;
} catch (e) {
return false;
}
}
/**
* @param {string} filePath
* @return {boolean}
*/
isExecutable(filePath) {
try {
fs.accessSync(filePath, fs.constants.X_OK);
return true;
} catch (e) {
return false;
}
}
/**
* @param {string} somePath
* @return {!Array<string>}
*/
split(somePath) {
somePath = path.normalize(somePath);
if (somePath.endsWith(path.sep))
somePath = somePath.substring(0, somePath.length - path.sep.length);
return somePath.split(path.sep);
}
/**
* @param {string} path1
* @param {string} path2
* @return {string}
*/
join(...args) {
if (args[0] === '' && args.length > 1)
args[0] = path.sep;
args = args.filter(part => typeof part === 'string');
return path.join.apply(path, args);
}
/**
* @param {string} filePath
* @param {(string|!Object)} option
* @return {!FileDescriptor}
*/
open(filePath, option) {
if (typeof option === 'string')
return new FileDescriptor(filePath, option);
return new FileDescriptor(filePath, option.mode);
}
}
var fdwrite = deasync(fs.write);
var fdread = deasync(fs.read);
class FileDescriptor {
/**
* @param {string} filePath
* @param {string} mode
*/
constructor(filePath, mode) {
this._position = 0;
this._encoding = 'utf8';
if (mode === 'rb') {
this._mode = 'r';
this._encoding = 'latin1';
} else if (mode === 'wb' || mode === 'b') {
this._mode = 'w';
this._encoding = 'latin1';
} else if (mode === 'rw+') {
this._mode = 'a+';
this._position = fs.existsSync(filePath) ? fs.statSync(filePath).size : 0;
} else {
this._mode = mode;
}
this._fd = fs.openSync(filePath, this._mode);
}
/**
* @param {string} data
*/
write(data) {
var buffer = Buffer.from(data, this._encoding);
var written = fdwrite(this._fd, buffer, 0, buffer.length, this._position);
this._position += written;
}
getEncoding() {
return 'UTF-8';
}
/**
* @param {string} data
*/
writeLine(data) {
this.write(data + '\n');
}
/**
* @param {number=} size
* @return {string}
*/
read(size) {
var position = this._position;
if (!size) {
size = fs.fstatSync(this._fd).size;
position = 0;
}
var buffer = new Buffer(size);
var bytesRead = fdread(this._fd, buffer, 0, size, position);
this._position += bytesRead;
return buffer.toString(this._encoding);
}
flush() {
// noop.
}
/**
* @param {number} position
*/
seek(position) {
this._position = position;
}
close() {
fs.closeSync(this._fd);
}
/**
* @return {boolean}
*/
atEnd() {
}
}
module.exports = FileSystem;

139
phantomjs/Phantom.js Normal file
View File

@ -0,0 +1,139 @@
/**
* Copyright 2017 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var fs = require('fs');
var path = require('path');
var vm = require('vm');
var url = require('url');
/**
* @param {!Object} context
* @param {string} scriptPath
*/
module.exports.create = function(context, scriptPath) {
var phantom = {
page: {
onConsoleMessage: null,
},
/**
* @param {string} relative
* @param {string} base
* @return {string}
*/
resolveRelativeUrl: function(relative, base) {
return url.resolve(base, relative);
},
/**
* @param {string} url
* @return {string}
*/
fullyDecodeUrl: function(url) {
return decodeURI(url);
},
libraryPath: path.dirname(scriptPath),
onError: null,
/**
* @return {string}
*/
get outputEncoding() {
return 'UTF-8';
},
/**
* @param {string} value
*/
set outputEncoding(value) {
throw new Error('Phantom.outputEncoding setter is not implemented');
},
/**
* @return {boolean}
*/
get cookiesEnabled() {
return true;
},
/**
* @param {boolean} value
*/
set cookiesEnabled(value) {
throw new Error('Phantom.cookiesEnabled setter is not implemented');
},
/**
* @return {!{major: number, minor: number, patch: number}}
*/
get version() {
var versionParts = require('../package.json').version.split('.');
return {
major: parseInt(versionParts[0], 10),
minor: parseInt(versionParts[1], 10),
patch: parseInt(versionParts[2], 10),
};
},
/**
* @param {number=} code
*/
exit: function(code) {
process.exit(code);
},
/**
* @param {string} filePath
* @return {boolean}
*/
injectJs: function(filePath) {
filePath = path.resolve(phantom.libraryPath, filePath);
if (!fs.existsSync(filePath))
return false;
var code = fs.readFileSync(filePath, 'utf8');
if (code.startsWith('#!'))
code = code.substring(code.indexOf('\n'));
vm.runInContext(code, context, {
filename: filePath,
displayErrors: true
});
return true;
},
/**
* @param {string} moduleSource
* @param {string} filename
*/
loadModule: function(moduleSource, filename) {
var code = [
"(function(require, exports, module) {\n",
moduleSource,
"\n}.call({},",
"require.cache['" + filename + "']._getRequire(),",
"require.cache['" + filename + "'].exports,",
"require.cache['" + filename + "']",
"));"
].join('');
vm.runInContext(code, context, {
filename: filename,
displayErrors: true
});
},
};
return phantom;
}

118
phantomjs/System.js Normal file
View File

@ -0,0 +1,118 @@
/**
* Copyright 2017 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var readline = require('readline');
var await = require('./utilities').await;
var os = require('os');
class System {
/**
* @param {!Array<string>} args
*/
constructor(args) {
this.args = args;
this.env = {};
Object.assign(this.env, process.env);
this.stdin = new StandardInput(process.stdin);
this.stdout = new StandardOutput(process.stdout);
this.stderr = new StandardOutput(process.stderr);
this.platform = "phantomjs";
this.pid = process.pid;
this.isSSLSupported = false;
this.os = {
architecture: os.arch(),
name: os.type(),
version: os.release()
};
}
}
class StandardInput {
/**
* @param {!Readable} readableStream
*/
constructor(readableStream) {
this._readline = readline.createInterface({
input: readableStream
});
this._lines = [];
this._closed = false;
this._readline.on('line', line => this._lines.push(line));
this._readline.on('close', () => this._closed = true);
}
/**
* @return {string}
*/
readLine() {
if (this._closed && !this._lines.length)
return '';
if (!this._lines.length) {
var linePromise = new Promise(fulfill => this._readline.once('line', fulfill));
await(linePromise);
}
return this._lines.shift();
}
/**
* @return {string}
*/
read() {
if (!this._closed) {
var closePromise = new Promise(fulfill => this._readline.once('close', fulfill));
await(closePromise);
}
var text = this._lines.join('\n');
this._lines = [];
return text;
}
close() {
this._readline.close();
}
}
class StandardOutput {
/**
* @param {!Writable} writableStream
*/
constructor(writableStream) {
this._stream = writableStream;
}
/**
* @param {string} data
*/
write(data) {
this._stream.write(data);
}
/**
* @param {string} data
*/
writeLine(data) {
this._stream.write(data + '\n');
}
flush() {
}
close() {
this._stream.end();
}
}
module.exports = System;

327
phantomjs/WebPage.js Normal file
View File

@ -0,0 +1,327 @@
/**
* Copyright 2017 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var await = require('./utilities').await;
var EventEmitter = require('events');
var fs = require('fs');
var path = require('path');
var mime = require('mime');
var PageEvents = require('../lib/Page').Events;
var ScreenshotTypes = require('../lib/Page').ScreenshotTypes;
var noop = function() { };
class WebPage {
/**
* @param {!Browser} browser
* @param {string} scriptPath
* @param {!Object=} options
*/
constructor(browser, scriptPath, options) {
this._page = await(browser.newPage());
this.settings = new WebPageSettings(this._page);
options = options || {};
options.settings = options.settings || {};
if (options.settings.userAgent)
this.settings.userAgent = options.settings.userAgent;
if (options.viewportSize)
await(this._page.setSize(options.viewportSize));
await(this._page.setInPageCallback('callPhantom', (...args) => {
return this.onCallback.apply(null, args);
}));
this.clipRect = options.clipRect || {left: 0, top: 0, width: 0, height: 0};
this.onCallback = null;
this.onConsoleMessage = null;
this.onLoadFinished = null;
this.onResourceError = null;
this.onResourceReceived = null;
this.libraryPath = path.dirname(scriptPath);
this._onConfirm = undefined;
this._onError = noop;
this._pageEvents = new AsyncEmitter(this._page);
this._pageEvents.on(PageEvents.ResponseReceived, response => this._onResponseReceived(response));
this._pageEvents.on(PageEvents.ResourceLoadingFailed, event => (this.onResourceError || noop).call(null, event));
this._pageEvents.on(PageEvents.ConsoleMessageAdded, msg => (this.onConsoleMessage || noop).call(null, msg));
this._pageEvents.on(PageEvents.DialogOpened, dialog => this._onDialog(dialog));
this._pageEvents.on(PageEvents.ExceptionThrown, (exception, stack) => (this._onError || noop).call(null, exception, stack));
}
_onResponseReceived(response) {
if (!this.onResourceReceived)
return;
var headers = [];
for (var key in response.headers) {
headers.push({
name: key,
value: response.headers[key]
});
}
response.headers = headers;
this.onResourceReceived.call(null, response);
}
/**
* @param {string} url
* @param {function()} callback
*/
includeJs(url, callback) {
this._page.evaluateAsync(include, url).then(callback);
function include(url) {
var script = document.createElement('script');
script.src = url;
var promise = new Promise(x => script.onload = x);
document.head.appendChild(script);
return promise;
}
}
/**
* @return {!{width: number, height: number}}
*/
get viewportSize() {
return this._page.size();
}
/**
* @return {!Object}
*/
get customHeaders() {
return this._page.extraHTTPHeaders();
}
/**
* @param {!Object} value
*/
set customHeaders(value) {
await(this._page.setExtraHTTPHeaders(value));
}
/**
* @param {string} filePath
*/
injectJs(filePath) {
if (!fs.existsSync(filePath))
filePath = path.resolve(this.libraryPath, filePath);
if (!fs.existsSync(filePath))
return;
var code = fs.readFileSync(filePath, 'utf8');
this.evaluate(code);
}
/**
* @return {string}
*/
get plainText() {
return await(this._page.plainText());
}
/**
* @return {string}
*/
get title() {
return await(this._page.title());
}
/**
* @return {(function()|undefined)}
*/
get onError() {
return this._onError;
}
/**
* @param {(function()|undefined)} handler
*/
set onError(handler) {
if (typeof handler !== 'function')
handler = undefined;
this._onError = handler;
}
/**
* @return {(function()|undefined)}
*/
get onConfirm() {
return this._onConfirm;
}
/**
* @param {function()} handler
*/
set onConfirm(handler) {
if (typeof handler !== 'function')
handler = undefined;
this._onConfirm = handler;
}
_onDialog(dialog) {
if (!this._onConfirm)
return;
var accept = this._onConfirm.call(null);
await(this._page.handleDialog(accept));
}
/**
* @return {string}
*/
get url() {
return await(this._page.url());
}
/**
* @param {string} html
*/
set content(html) {
await(this._page.setContent(html));
}
/**
* @param {string} html
* @param {function()=} callback
*/
open(url, callback) {
console.assert(arguments.length <= 2, 'WebPage.open does not support METHOD and DATA arguments');
this._page.navigate(url).then(result => {
var status = result ? 'success' : 'fail';
if (!result) {
this.onResourceError.call(null, {
url,
errorString: 'SSL handshake failed'
});
}
if (this.onLoadFinished)
this.onLoadFinished.call(null, status);
if (callback)
callback.call(null, status);
});
}
/**
* @param {!{width: number, height: number}} options
*/
set viewportSize(options) {
await(this._page.setSize(options));
}
/**
* @param {function()} fun
* @param {!Array<!Object>} args
*/
evaluate(fun, ...args) {
return await(this._page.evaluate(fun, ...args));
}
/**
* {string} fileName
*/
render(fileName) {
var mimeType = mime.lookup(fileName);
var screenshotType = null;
if (mimeType === 'image/png')
screenshotType = ScreenshotTypes.PNG;
else if (mimeType === 'image/jpeg')
screenshotType = ScreenshotTypes.JPG;
if (!screenshotType)
throw new Error(`Cannot render to file ${fileName} - unsupported mimeType ${mimeType}`);
var clipRect = null;
if (this.clipRect && (this.clipRect.left || this.clipRect.top || this.clipRect.width || this.clipRect.height)) {
clipRect = {
x: this.clipRect.left,
y: this.clipRect.top,
width: this.clipRect.width,
height: this.clipRect.height
};
}
var imageBuffer = await(this._page.screenshot(screenshotType, clipRect));
fs.writeFileSync(fileName, imageBuffer);
}
release() {
this._page.close();
}
close() {
this._page.close();
}
}
class WebPageSettings {
/**
* @param {!Page} page
*/
constructor(page) {
this._page = page;
}
/**
* @param {string} value
*/
set userAgent(value) {
await(this._page.setUserAgentOverride(value));
}
/**
* @return {string}
*/
get userAgent() {
return this._page.userAgentOverride();
}
}
// To prevent reenterability, eventemitters should emit events
// only being in a consistent state.
// This is not the case for 'ws' npm module: https://goo.gl/sy3dJY
//
// Since out phantomjs environment uses nested event loops, we
// exploit this condition in 'ws', which probably never happens
// in case of regular I/O.
//
// This class is a wrapper around EventEmitter which re-emits events asynchronously,
// helping to overcome the issue.
class AsyncEmitter extends EventEmitter {
/**
* @param {!Page} page
*/
constructor(page) {
super();
this._page = page;
this._symbol = Symbol('AsyncEmitter');
this.on('newListener', this._onListenerAdded);
this.on('removeListener', this._onListenerRemoved);
}
_onListenerAdded(event, listener) {
// Async listener calls original listener on next tick.
var asyncListener = (...args) => {
process.nextTick(() => listener.apply(null, args));
};
listener[this._symbol] = asyncListener;
this._page.on(event, asyncListener);
}
_onListenerRemoved(event, listener) {
this._page.removeListener(event, listener[this._symbol]);
}
}
module.exports = WebPage;

83
phantomjs/WebServer.js Normal file
View File

@ -0,0 +1,83 @@
/**
* Copyright 2017 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var http = require('http');
var await = require('./utilities').await;
class WebServer {
constructor() {
this._server = http.createServer();
this.objectName = 'WebServer';
this.listenOnPort = this.listen;
this.newRequest = function(req, res) { }
Object.defineProperty(this, 'port', {
get: () => {
if (!this._server.listening)
return '';
return this._server.address().port + '';
},
enumerable: true,
configurable: false
});
}
close() {
this._server.close();
}
/**
* @param {nubmer} port
* @return {boolean}
*/
listen(port, callback) {
if (this._server.listening)
return false;
this.newRequest = callback;
this._server.listen(port);
var errorPromise = new Promise(x => this._server.once('error', x));
var successPromise = new Promise(x => this._server.once('listening', x));
await(Promise.race([errorPromise, successPromise]));
if (!this._server.listening)
return false;
this._server.on('request', (req, res) => {
res.close = res.end.bind(res);
var headers = res.getHeaders();
res.headers = [];
for (var key in headers) {
res.headers.push({
name: key,
value: headers[key]
});
}
res.header = res.getHeader;
res.setHeaders = (headers) => {
for (var key in headers)
res.setHeader(key, headers[key]);
}
Object.defineProperty(res, 'statusCode', {
enumerable: true,
configurable: true,
writable: true,
value: res.statusCode
});
this.newRequest.call(null, req, res);
});
return true;
}
}
module.exports = WebServer;

71
phantomjs/index.js Normal file
View File

@ -0,0 +1,71 @@
/**
* Copyright 2017 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var vm = require('vm');
var path = require('path');
var fs = require('fs');
var Phantom = require('./Phantom');
var FileSystem = require('./FileSystem');
var System = require('./System');
var WebPage = require('./WebPage');
var WebServer = require('./WebServer');
var child_process = require('child_process');
var bootstrapPath = path.join(__dirname, '..', 'third_party', 'phantomjs', 'bootstrap.js');
var bootstrapCode = fs.readFileSync(bootstrapPath, 'utf8');
module.exports = {
/**
* @param {!Browser} browser
* @param {string} scriptPath
* @param {!Array<string>} argv
* @return {!Object}
*/
createContext(browser, scriptPath, argv) {
var context = {};
context.setInterval = setInterval;
context.setTimeout = setTimeout;
context.clearInterval = clearInterval;
context.clearTimeout = clearTimeout;
context.phantom = Phantom.create(context, scriptPath);
context.console = console;
context.window = context;
context.WebPage = (options) => new WebPage(browser, scriptPath, options);
vm.createContext(context);
var nativeExports = {
fs: new FileSystem(),
system: new System(argv._),
webpage: {
create: context.WebPage,
},
webserver: {
create: () => new WebServer(),
},
cookiejar: {
create: () => {},
},
child_process: child_process
};
vm.runInContext(bootstrapCode, context, {
filename: 'bootstrap.js'
})(nativeExports);
return context;
}
}

33
phantomjs/utilities.js Normal file
View File

@ -0,0 +1,33 @@
/**
* Copyright 2017 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var loopWhile = require('deasync').loopWhile;
module.exports = {
await: function(promise) {
var error;
var result;
var done = false;
promise.then(r => result = r)
.catch(err => error = err)
.then(() => done = true);
loopWhile(() => !done);
if (error)
throw error;
return result;
}
}

19
third_party/phantomjs/CHANGES.md vendored Normal file
View File

@ -0,0 +1,19 @@
Short Name: phantomjs
URL: https://github.com/ariya/phantomjs/tree/2.1.1
Version: 2.1.1
License: BSD
License File: LICENSE.BSD
Security Critical: no
Description:
This package is used to aid puppeteer in running phantom.js scripts:
- test/ - testsuite is used to validate puppeteer running phantom.js scripts
- boostrap.js - used to bootstrap puppeteer environment
Local Modifications:
- test/run_test.py was changed to run puppeteer instead of phantomjs
- Certain tests under test/ were changed where tests were unreasonably strict in their expectations
(e.g. validating the exact format of error messages)
- bootstrap.js was changed to accept native modules as function arguments.
- test/run_test.py was enhanced to support "unsupported" directive

22
third_party/phantomjs/LICENSE.BSD vendored Normal file
View File

@ -0,0 +1,22 @@
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

235
third_party/phantomjs/bootstrap.js vendored Normal file
View File

@ -0,0 +1,235 @@
/*jslint sloppy: true, nomen: true */
/*global window:true,phantom:true */
/*
This file is part of the PhantomJS project from Ofi Labs.
Copyright (C) 2011 Ariya Hidayat <ariya.hidayat@gmail.com>
Copyright (C) 2011 Ivan De Marino <ivan.de.marino@gmail.com>
Copyright (C) 2011 James Roe <roejames12@hotmail.com>
Copyright (C) 2011 execjosh, http://execjosh.blogspot.com
Copyright (C) 2012 James M. Greene <james.m.greene@gmail.com>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
(function(nativeExports) {
// CommonJS module implementation follows
window.global = window;
// fs is loaded at the end, when everything is ready
var fs;
var cache = {};
var paths = [];
var extensions = {
'.js': function(module, filename) {
var code = fs.read(filename);
module._compile(code);
},
'.json': function(module, filename) {
module.exports = JSON.parse(fs.read(filename));
}
};
function dirname(path) {
var replaced = path.replace(/\/[^\/]*\/?$/, '');
if (replaced == path) {
replaced = '';
}
return replaced;
}
function basename(path) {
return path.replace(/.*\//, '');
}
function joinPath() {
// It should be okay to hard-code a slash here.
// The FileSystem module returns a platform-specific
// separator, but the JavaScript engine only expects
// the slash.
var args = Array.prototype.slice.call(arguments);
return args.join('/');
}
function tryFile(path) {
if (fs.isFile(path)) return path;
return null;
}
function tryExtensions(path) {
var filename, exts = Object.keys(extensions);
for (var i=0; i<exts.length; ++i) {
filename = tryFile(path + exts[i]);
if (filename) return filename;
}
return null;
}
function tryPackage(path) {
var filename, package, packageFile = joinPath(path, 'package.json');
if (fs.isFile(packageFile)) {
package = JSON.parse(fs.read(packageFile));
if (!package || !package.main) return null;
filename = fs.absolute(joinPath(path, package.main));
return tryFile(filename) || tryExtensions(filename) ||
tryExtensions(joinPath(filename, 'index'));
}
return null;
}
function Module(filename, stubs) {
if (filename) this._setFilename(filename);
this.exports = {};
this.stubs = {};
for (var name in stubs) {
this.stubs[name] = stubs[name];
}
}
Module.prototype._setFilename = function(filename) {
this.id = this.filename = filename;
this.dirname = dirname(filename);
};
Module.prototype._isNative = function() {
return this.filename && this.filename[0] === ':';
};
Module.prototype._getPaths = function(request) {
var _paths = [], dir;
if (request[0] === '.') {
_paths.push(fs.absolute(joinPath(phantom.webdriverMode ? ":/ghostdriver" : this.dirname, request)));
} else if (fs.isAbsolute(request)) {
_paths.push(fs.absolute(request));
} else {
// first look in PhantomJS modules
_paths.push(joinPath(':/modules', request));
// then look in node_modules directories
if (!this._isNative()) {
dir = this.dirname;
while (dir) {
_paths.push(joinPath(dir, 'node_modules', request));
dir = dirname(dir);
}
}
}
for (var i=0; i<paths.length; ++i) {
if(fs.isAbsolute(paths[i])) {
_paths.push(fs.absolute(joinPath(paths[i], request)));
} else {
_paths.push(fs.absolute(joinPath(this.dirname, paths[i], request)));
}
}
return _paths;
};
Module.prototype._getFilename = function(request) {
if (nativeExports[request])
return ':/modules/' + request + '.js';
var path, filename = null, _paths = this._getPaths(request);
for (var i=0; i<_paths.length && !filename; ++i) {
path = _paths[i];
filename = tryFile(path) || tryExtensions(path) || tryPackage(path) ||
tryExtensions(joinPath(path, 'index'));
}
return filename;
};
Module.prototype._getRequire = function() {
var self = this;
function require(request) {
return self.require(request);
}
require.cache = cache;
require.extensions = extensions;
require.paths = paths;
require.stub = function(request, exports) {
self.stubs[request] = { exports: exports };
};
return require;
};
Module.prototype._load = function() {
if (this._isNative())
return;
var ext = this.filename.match(/\.[^.]+$/)[0];
if (!ext) ext = '.js';
extensions[ext](this, this.filename);
};
Module.prototype._compile = function(code) {
phantom.loadModule(code, this.filename);
};
Module.prototype.require = function(request) {
var filename, module;
// first see if there are any stubs for the request
if (this.stubs.hasOwnProperty(request)) {
if (this.stubs[request].exports instanceof Function) {
this.stubs[request].exports = this.stubs[request].exports();
}
return this.stubs[request].exports;
}
// else look for a file
filename = this._getFilename(request);
if (!filename) {
throw new Error("Cannot find module '" + request + "'");
}
if (cache.hasOwnProperty(filename)) {
return cache[filename].exports;
}
module = new Module(filename, this.stubs);
if (module._isNative()) {
module.exports = nativeExports[request] || {};
}
cache[filename] = module;
module._load();
return module.exports;
};
(function() {
var cwd, mainFilename, mainModule = new Module();
window.require = mainModule._getRequire();
fs = nativeExports.fs;
cwd = fs.absolute(phantom.libraryPath);
mainFilename = joinPath(cwd, basename(require('system').args[0]) || 'repl');
mainModule._setFilename(mainFilename);
}());
})

View File

@ -0,0 +1,52 @@
"use strict";
var page = require('webpage').create();
page.viewportSize = { width: 400, height : 400 };
page.content = '<html><body><canvas id="surface"></canvas></body></html>';
page.evaluate(function() {
var el = document.getElementById('surface'),
context = el.getContext('2d'),
width = window.innerWidth,
height = window.innerHeight,
cx = width / 2,
cy = height / 2,
radius = width / 2.3,
imageData,
pixels,
hue, sat, value,
i = 0, x, y, rx, ry, d,
f, g, p, u, v, w, rgb;
el.width = width;
el.height = height;
imageData = context.createImageData(width, height);
pixels = imageData.data;
for (y = 0; y < height; y = y + 1) {
for (x = 0; x < width; x = x + 1, i = i + 4) {
rx = x - cx;
ry = y - cy;
d = rx * rx + ry * ry;
if (d < radius * radius) {
hue = 6 * (Math.atan2(ry, rx) + Math.PI) / (2 * Math.PI);
sat = Math.sqrt(d) / radius;
g = Math.floor(hue);
f = hue - g;
u = 255 * (1 - sat);
v = 255 * (1 - sat * f);
w = 255 * (1 - sat * (1 - f));
pixels[i] = [255, v, u, u, w, 255, 255][g];
pixels[i + 1] = [w, 255, 255, v, u, u, w][g];
pixels[i + 2] = [u, u, w, 255, 255, v, u][g];
pixels[i + 3] = 255;
}
}
}
context.putImageData(imageData, 0, 0);
document.body.style.backgroundColor = 'white';
document.body.style.margin = '0px';
});
page.render('colorwheel.png');
phantom.exit();

View File

@ -0,0 +1,8 @@
//! no-harness
//! expect-exit: 0
//! expect-stdout: "we are alive"
var sys = require('system');
sys.stdout.write("we are alive\n");
phantom.exit();
sys.stdout.write("ERROR control passed beyond phantom.exit");

View File

@ -0,0 +1,8 @@
//! no-harness
//! expect-exit: 23
//! expect-stdout: "we are alive"
var sys = require('system');
sys.stdout.write("we are alive\n");
phantom.exit(23);
sys.stdout.write("ERROR control passed beyond phantom.exit");

View File

@ -0,0 +1,27 @@
// Test the properties of the 'module' object.
// Assumes the 'dummy_exposed' module is to be found in a directory
// named 'node_modules'.
// Module load might fail, so do it in a setup function.
var module;
setup(function () {
module = require("dummy_exposed");
});
test(function() {
assert_regexp_match(module.filename, /\/node_modules\/dummy_exposed\.js$/);
}, "module.filename is the absolute pathname of the module .js file");
test(function() {
assert_regexp_match(module.dirname, /\/node_modules$/);
}, "module.dirname is the absolute pathname of the directory containing "+
"the module");
test(function() {
assert_equals(module.id, module.filename);
}, "module.id equals module.filename");
test(function() {
var dummy_file = module.require('./dummy_file');
assert_equals(dummy_file, 'spec/node_modules/dummy_file');
}, "module.require is callable and resolves relative to the module");

View File

@ -0,0 +1,39 @@
test(function () {
assert_type_of(phantom, 'object');
}, "phantom object");
test(function () {
assert_own_property(phantom, 'libraryPath');
assert_type_of(phantom.libraryPath, 'string');
assert_greater_than(phantom.libraryPath.length, 0);
}, "phantom.libraryPath");
test(function () {
assert_own_property(phantom, 'outputEncoding');
assert_type_of(phantom.outputEncoding, 'string');
assert_equals(phantom.outputEncoding.toLowerCase(), 'utf-8'); // default
}, "phantom.outputEncoding");
test(function () {
assert_own_property(phantom, 'injectJs');
assert_type_of(phantom.injectJs, 'function');
}, "phantom.injectJs");
test(function () {
assert_own_property(phantom, 'exit');
assert_type_of(phantom.exit, 'function');
}, "phantom.exit");
test(function () {
assert_own_property(phantom, 'cookiesEnabled');
assert_type_of(phantom.cookiesEnabled, 'boolean');
assert_is_true(phantom.cookiesEnabled);
}, "phantom.cookiesEnabled");
test(function () {
assert_own_property(phantom, 'version');
assert_type_of(phantom.version, 'object');
assert_type_of(phantom.version.major, 'number');
assert_type_of(phantom.version.minor, 'number');
assert_type_of(phantom.version.patch, 'number');
}, "phantom.version");

View File

@ -0,0 +1,10 @@
/* The require tests need to run inside a module to work correctly; that
module is require/require_spec.js. (That directory also contains a
bunch of other files used by this test.) The module exports an array
of test functions in the form expected by generate_tests(). */
var rtests = require("require/require_spec.js").tests;
for (var i = 0; i < rtests.length; i++) {
test.apply(null, rtests[i]);
}

View File

@ -0,0 +1,2 @@
var b = require('./b');
exports.b = b;

View File

@ -0,0 +1,2 @@
var a = require('./a');
exports.a = a;

View File

@ -0,0 +1 @@
module.exports = 'dir/dummy';

View File

@ -0,0 +1 @@
module.exports = 'subdir/dummy';

View File

@ -0,0 +1 @@
exports.dummyFile2 = require('dummy_file2');

View File

@ -0,0 +1 @@
module.exports = 'require/subdir2/loader'

View File

@ -0,0 +1 @@
module.exports = 'require/dummy';

View File

View File

@ -0,0 +1,3 @@
{
"message": "hello"
}

View File

@ -0,0 +1 @@
module.exports = 'require/node_modules/dummy_file';

View File

@ -0,0 +1 @@
module.exports = 'require/node_modules/dummy_module';

View File

@ -0,0 +1,4 @@
{
"name": "dummy",
"main": "./libdir/dummy_module.js"
}

View File

@ -0,0 +1 @@
module.exports = 'require/node_modules/dummy_module2';

View File

@ -0,0 +1,3 @@
exports.requireNonExistent = function() {
require('./non_existent');
};

View File

@ -0,0 +1,131 @@
var fs = require('fs');
var tests = [];
exports.tests = tests;
tests.push([function () {
assert_no_property(window, 'CoffeeScript');
assert_own_property(window, 'require');
assert_own_property(require('webpage'), 'create');
assert_own_property(require('webserver'), 'create');
assert_own_property(require('cookiejar'), 'create');
assert_own_property(require('fs'), 'separator');
assert_equals(require('system').platform, 'phantomjs');
}, "native modules"]);
tests.push([function () {
assert_equals(require('./json_dummy').message, 'hello');
assert_equals(require('./dummy.js'), 'require/dummy');
}, "JS and JSON modules"]);
tests.push([function () {
require('./empty').hello = 'hola';
assert_equals(require('./empty').hello, 'hola');
// assert_own_property rejects Functions
assert_equals(require.hasOwnProperty('cache'), true);
var exposed = require('dummy_exposed');
assert_equals(require.cache[exposed.filename], exposed);
}, "module caching"]);
tests.push([function () {
var a = require('./a');
var b = require('./b');
assert_equals(a.b, b);
assert_equals(b.a, a);
}, "circular dependencies"]);
tests.push([function () {
assert_throws("Cannot find module 'dummy_missing'",
function () { require('dummy_missing'); });
try {
require('./not_found').requireNonExistent();
} catch (e) {
assert_regexp_match(e.stack, /at require /);
}
}, "error handling 1"]);
tests.push([function error_handling_2 () {
try {
require('./thrower').fn();
} catch (e) {
assert_regexp_match(e.toString() + "\n" + e.stack,
/^Error: fn\nError: fn\n at Object.thrower/);
}
}, "error handling 2"]);
tests.push([function () {
assert_equals(require('./stubber').stubbed, 'stubbed module');
assert_equals(require('./stubber').child.stubbed, 'stubbed module');
assert_throws("Cannot find module 'stubbed'",
function () { require('stubbed'); });
var count = 0;
require.stub('lazily_stubbed', function() {
++count;
return 'lazily stubbed module';
});
assert_equals(require('lazily_stubbed'), 'lazily stubbed module');
require('lazily_stubbed');
assert_equals(count, 1);
}, "stub modules"]);
tests.push([function () {
assert_equals(require('./dummy'), 'require/dummy');
assert_equals(require('../fixtures/dummy'), 'spec/dummy');
assert_equals(require('./dir/dummy'), 'dir/dummy');
assert_equals(require('./dir/subdir/dummy'), 'subdir/dummy');
assert_equals(require('./dir/../dummy'), 'require/dummy');
assert_equals(require('./dir/./dummy'), 'dir/dummy');
assert_equals(require(
fs.absolute(module.dirname + '/dummy.js')), 'require/dummy');
}, "relative and absolute paths"]);
tests.push([function () {
assert_equals(require('dummy_file'), 'require/node_modules/dummy_file');
assert_equals(require('dummy_file2'), 'spec/node_modules/dummy_file2');
assert_equals(require('./dir/subdir/loader').dummyFile2,
'spec/node_modules/dummy_file2');
assert_equals(require('dummy_module'),
'require/node_modules/dummy_module');
assert_equals(require('dummy_module2'),
'require/node_modules/dummy_module2');
}, "loading from node_modules"]);
function require_paths_tests_1 () {
assert_equals(require('loader').dummyFile2,
'spec/node_modules/dummy_file2');
assert_equals(require('../subdir2/loader'),
'require/subdir2/loader');
assert_equals(require('../fixtures/dummy'), 'spec/dummy');
}
function require_paths_tests_2 () {
assert_throws("Cannot find module 'loader'",
function () { require('loader'); });
}
tests.push([function () {
require.paths.push('dir/subdir');
this.add_cleanup(function () { require.paths.pop(); });
require_paths_tests_1();
}, "relative paths in require.paths"]);
tests.push([
require_paths_tests_2, "relative paths in require paths (after removal)"]);
tests.push([function () {
require.paths.push(fs.absolute(module.dirname + '/dir/subdir'));
this.add_cleanup(function () { require.paths.pop(); });
require_paths_tests_1();
}, "absolute paths in require.paths"]);
tests.push([
require_paths_tests_2, "relative paths in require paths (after removal)"]);

View File

@ -0,0 +1,5 @@
require.stub('stubbed', 'stubbed module');
exports.stubbed = require('stubbed');
try {
exports.child = require('./stubber_child');
} catch (e) {}

View File

@ -0,0 +1 @@
exports.stubbed = require('stubbed');

View File

@ -0,0 +1,3 @@
exports.fn = function thrower() {
throw new Error('fn');
};

View File

@ -0,0 +1,14 @@
//! unsupported
// A SyntaxError leaks to phantom.onError, despite the try-catch.
setup({ allow_uncaught_exception: true });
test(function () {
var helperFile = "../fixtures/parse-error-helper.js";
try {
phantom.injectJs(helperFile);
} catch (e) {
assert_equals(e.stack[0].file, helperFile);
assert_equals(e.stack[0].line, 2);
}
}, "stack trace from syntax error in injected file");

View File

@ -0,0 +1,33 @@
//! unsupported
/* Test the test server itself. */
var webpage = require('webpage');
function test_one_page(url) {
var page = webpage.create();
page.onResourceReceived = this.step_func(function (response) {
assert_equals(response.status, 200);
});
page.onResourceError = this.unreached_func();
page.onResourceTimeout = this.unreached_func();
page.onLoadFinished = this.step_func_done(function (status) {
assert_equals(status, 'success');
});
page.open(url);
}
function do_test(path) {
var http_url = TEST_HTTP_BASE + path;
var https_url = TEST_HTTPS_BASE + path;
var http_test = async_test(http_url);
var https_test = async_test(https_url);
http_test.step(test_one_page, null, http_url);
https_test.step(test_one_page, null, https_url);
}
[
'hello.html',
'status?200',
'echo'
]
.forEach(do_test);

View File

@ -0,0 +1,7 @@
//! unsupported
//! no-harness
//! expect-exit: -15
//! expect-stderr: TIMEOUT: Process terminated after 0.25 seconds.
//! timeout: 0.25
// no code, so phantom will just sleep forever

View File

@ -0,0 +1,16 @@
// These are cursory tests; we assume the underlying Qt
// features are properly tested elsewhere.
test(function () {
assert_equals(
phantom.resolveRelativeUrl(
"../scripts/foo.js",
"http://example.com/topic/page.html"),
"http://example.com/scripts/foo.js");
assert_equals(
phantom.fullyDecodeUrl(
"https://ja.wikipedia.org/wiki/%E8%87%A8%E6%B5%B7%E5%AD%A6%E6%A0%A1"),
"https://ja.wikipedia.org/wiki/臨海学校");
}, "resolveRelativeUrl and fullyDecodeUrl");

View File

@ -0,0 +1,7 @@
// This is separate from basics/phantom-object.js because it has to be
// updated with every release.
test(function () {
assert_equals(phantom.version.major, 0);
assert_equals(phantom.version.minor, 0);
assert_equals(phantom.version.patch, 1);
}, "PhantomJS version number is accurate");

View File

@ -0,0 +1,18 @@
-----BEGIN CERTIFICATE-----
MIIC+zCCAeOgAwIBAgIJAJ7HwZBrgnLwMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV
BAMMCWxvY2FsaG9zdDAeFw0xNTA4MTEyMjU4MTZaFw0yNTA4MTAyMjU4MTZaMBQx
EjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
ggEBANFha8c5JKjYrHc7BTqmuFSAxYsSKbUUa0k+0PFpjhj7Io/NOeHhxfdLJX/B
LVQXEDhvOlSTDBgC3RQkxCZJmMzKZjMDlj0cxY0esZtcqt0sRpwRvT+EBE9SlFu4
TWM2BQ6k5E4OIX/9aUk9HQ99pSjqmhu/7n76n/5DfqxGwkfVZengI1KwfezaB5+Q
wAvoS7tadROqTyynV1kd+OF9BJZwO1eR9lAiGc139J/BHegVcqdrI043oR+1vyTw
BFpodw4HYdJHNgo7DKAtmXoDAws5myqx2GcnVng1wyzu6LdM65nMV4/p5Y/Y6Ziy
RqeV1gVbtpxTcrLmWFnI8BRwFBUCAwEAAaNQME4wHQYDVR0OBBYEFPP1YOkZpJmE
x/W48Kwv2N1QC1oDMB8GA1UdIwQYMBaAFPP1YOkZpJmEx/W48Kwv2N1QC1oDMAwG
A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAA1NxsrmKxGecAS6TEHNBqFZ
9NhV23kXY5sdv8zl7HUmzR+vIBumd9lkSZdOwAy5/hmj6ACReSJ9f2xpyi0fOtx5
WZ8Vcrg9Qjuy17qmGi66yL860yr0h6hltzCWCi7e26Eybawm3/9PmbNV3Hcwgxug
D+gv4LZLlyj4JI4lg/8RVXaNXqGBZ39YhRH0LFVjbYiFWUGqzfAT9YBoC67Ov8Yv
Bl1PoV3sJcagx67X6y8ru+gecc/OOXKJHxSidhjRqhKB6WOWIPfugsMOl1g2FMPv
tuPFsIQNSaln7V+ECeDOipJOSp9KAyM5fNcVjldd/e4V+qwcyoOijFywNfSK10M=
-----END CERTIFICATE-----

View File

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDRYWvHOSSo2Kx3
OwU6prhUgMWLEim1FGtJPtDxaY4Y+yKPzTnh4cX3SyV/wS1UFxA4bzpUkwwYAt0U
JMQmSZjMymYzA5Y9HMWNHrGbXKrdLEacEb0/hARPUpRbuE1jNgUOpORODiF//WlJ
PR0PfaUo6pobv+5++p/+Q36sRsJH1WXp4CNSsH3s2gefkMAL6Eu7WnUTqk8sp1dZ
HfjhfQSWcDtXkfZQIhnNd/SfwR3oFXKnayNON6Eftb8k8ARaaHcOB2HSRzYKOwyg
LZl6AwMLOZsqsdhnJ1Z4NcMs7ui3TOuZzFeP6eWP2OmYskanldYFW7acU3Ky5lhZ
yPAUcBQVAgMBAAECggEAOwI/w8fhAwz9niSuFpeB/57DDayywGveyKfBbygWegfc
97YZCAX/KvCswtKImdheI+mFAOzoTaQQ9mpeNYQsYhrwrpPmNZb0Pg9WcritFuQx
ii6drVbheBGH6kmI1dsVlcj25uCopE+g6pkkpYb9kwh7IjL3XiX4DUqsWpUej+ub
2iL/luW7nYHHIRqzOFgP3v/f29sFHNvYcgihphBMHtgb4VpeYQ/f7AC7k1bFYfA/
TmvfUcXdiPwJf0XICZOaLrT/6pigk0bRiLNn8npISu7Wlf4jF60bNAe4+krBVU4O
p8UjW99LiGKLDh8GpoudnzlnnngZ3SA5+bO7kwTjCQKBgQDvJwUShOWm2+5wJsr4
hWieTVDaZEDb+1WTe7DwtqRWNBWXchh8is9buWeXIe6+1WldBYYiQjgdggQCw8xG
IFFg1j1E6kPqk/kzrHYSsJ+/u8uaxypvvrVBhUqt5FduOxFojW2REX9W5n8HTdT4
32BGR4mGpuXzR+BsVK00QRgM+wKBgQDgIXtu6rbfx+mdXTFi6apWJoyu5bRWcrL2
mGoR+IjCk6NefcvBE33q54H/dk3+0Sxp6+uFo7lyKv4EL3lozQO2oga6sp2LOIEK
DUo+KQVOmntCNrjuN/PbjSu2s1j5QDnLNR9VvXGiYBWdpZ7k3YzoKJ1I4ZyB3kGs
H/lCXv52LwKBgER1HvaWJEcHXdGsyR0q0y+9Yg+h8w8FexGkrpm5LoGely+q8Wd1
NLZE9GpGxFjMLkT6d9MGsZmAxjUkZy0Lwz+9E/zOMnLLuOIZ1BK1jIUN9NJxgKxM
IwaGaUItwvlC31DWay7Dm3f8sxAcL4KuLpjvkWaCEAD76joYYxw6JfBRAoGADMe7
+xolLWN/3bpHq6U5UkpGcV6lxtwpekg8nCO44Kd8hFHWAX90CaYD0qZTUjlpN+z8
9BTe6TSsYV63pJM0KADbM2Al/Z9ONF2Hoz3BkLbcWm02ZFcKb7WADZ3yb9wKr5yq
2b/AsAqckO21vsUnWMGgHlzHCNy8j+0O0IsMJX8CgYAORhyGaU7x5t4kEvqBNIan
mOzuB0b5nYV9mHxmyFhOsa8LeM25SA4n1rFpTb8h6vmZF1y9+4Zy4uNfCR2wXg0v
I51qtZ8npbIksYvNqvHaTPg8ZBcFK5mHr3TDxXJCcc0ylzM98ze08D+qKr0joX4w
KlqN6KjGmYfb+RHehLk9sw==
-----END PRIVATE KEY-----

View File

@ -0,0 +1 @@
module.exports = 'spec/dummy';

View File

@ -0,0 +1,9 @@
ErrorHelper = {
foo: function() {
this.bar()
},
bar: function bar() {
referenceError
}
};

View File

@ -0,0 +1,2 @@
var ok;
bar("run away"

View File

@ -0,0 +1,36 @@
// Launch the official test suite for ECMA-262
var webpage = require('webpage');
page = webpage.create();
page.onError = function() {};
page.open('http://test262.ecmascript.org/', function() {
page.evaluate(function() { $('a#run').click(); });
page.evaluate(function() { $('img#btnRunAll').click(); });
function monitor() {
var data = page.evaluate(function() {
return {
ran: $('#totalCounter').text(),
total: $('#testsToRun').text(),
pass: $('#Pass').text(),
fail: $('#Fail').text(),
progress: $('div#progressbar').text()
};
});
console.log('Tests: ', data.ran, 'of', data.total,
' Pass:', data.pass, ' Fail:', data.fail);
if (data.progress.indexOf('complete') > 0) {
page.render('report.png');
phantom.exit();
} else {
setTimeout(monitor, 1000);
}
}
setTimeout(monitor, 0);
});

View File

@ -0,0 +1,93 @@
//! unsupported
var cookie0 = {
'name': 'Valid-Cookie-Name',
'value': 'Valid-Cookie-Value',
'domain': 'localhost',
'path': '/foo',
'httponly': true,
'secure': false
};
var cookie1 = {
'name': 'Valid-Cookie-Name-1',
'value': 'Valid-Cookie-Value',
'domain': 'localhost',
'path': '/foo',
'httponly': true,
'secure': false
};
var cookie2 = {
'name': 'Valid-Cookie-Name-2',
'value': 'Valid-Cookie-Value',
'domain': 'localhost',
'path': '/foo',
'httponly': true,
'secure': false
};
var cookies = [{
'name': 'Valid-Cookie-Name',
'value': 'Valid-Cookie-Value',
'domain': 'localhost',
'path': '/foo',
'httponly': true,
'secure': false
},{
'name': 'Valid-Cookie-Name-Sec',
'value': 'Valid-Cookie-Value-Sec',
'domain': 'localhost',
'path': '/foo',
'httponly': true,
'secure': false,
'expires': new Date().getTime() + 3600 //< expires in 1h
}];
var cookiejar, jar1, jar2;
setup(function () {
cookiejar = require('cookiejar');
jar1 = cookiejar.create();
jar2 = cookiejar.create();
});
test(function () {
assert_type_of(jar1, 'object');
assert_not_equals(jar1, null);
assert_type_of(jar1.cookies, 'object');
assert_type_of(jar1.addCookie, 'function');
assert_type_of(jar1.deleteCookie, 'function');
assert_type_of(jar1.clearCookies, 'function');
}, "cookie jar properties");
test(function () {
assert_equals(jar1.cookies.length, 0);
jar1.addCookie(cookie0);
assert_equals(jar1.cookies.length, 1);
jar1.deleteCookie('Valid-Cookie-Name');
assert_equals(jar1.cookies.length, 0);
}, "adding and removing cookies");
test(function () {
assert_equals(jar1.cookies.length, 0);
jar1.cookies = cookies;
assert_equals(jar1.cookies.length, 2);
jar1.clearCookies();
assert_equals(jar1.cookies.length, 0);
}, "setting and clearing a cookie jar");
test(function () {
jar1.addCookie(cookie1);
assert_equals(jar1.cookies.length, 1);
assert_equals(jar2.cookies.length, 0);
jar2.addCookie(cookie2);
jar1.deleteCookie('Valid-Cookie-Name-1');
assert_equals(jar1.cookies.length, 0);
assert_equals(jar2.cookies.length, 1);
jar1.close();
jar2.close();
}, "cookie jar isolation");

View File

@ -0,0 +1,52 @@
//! unsupported
var cookies = {
'beforeExpires': {
'name': 'beforeExpires',
'value': 'expireValue',
'domain': '.abc.com',
'path': '/',
'httponly': false,
'secure': false,
'expires': 'Tue, 10 Jun 2025 12:28:29 GMT'
},
'noExpiresDate': {
'name': 'noExpiresDate',
'value': 'value',
'domain': '.abc.com',
'path': '/',
'httponly': false,
'secure': false,
'expires': null
},
'afterExpires': {
'name': 'afterExpires',
'value': 'value',
'domain': '.abc.com',
'path': '/',
'httponly': false,
'secure': false,
'expires': 'Mon, 10 Jun 2024 12:28:29 GMT'
}
};
test(function () {
var i, c, d, prop;
for (i in cookies) {
if (!cookies.hasOwnProperty(i)) continue;
phantom.addCookie(cookies[i]);
}
for (i in phantom.cookies) {
d = phantom.cookies[i];
c = cookies[d.name];
for (prop in c) {
if (!c.hasOwnProperty(prop)) continue;
if (c[prop] === null) {
assert_no_property(d, prop);
} else {
assert_own_property(d, prop);
assert_equals(c[prop], d[prop]);
}
}
}
}, "optional cookie properties should not leak");

View File

@ -0,0 +1,220 @@
// Basic Files API (read, write, remove, ...)
var FILENAME = "temp-01.test",
FILENAME_COPY = FILENAME + ".copy",
FILENAME_MOVED = FILENAME + ".moved",
FILENAME_EMPTY = FILENAME + ".empty",
FILENAME_ENC = FILENAME + ".enc",
FILENAME_BIN = FILENAME + ".bin",
ABSENT = "absent-01.test";
var fs;
setup(function () {
fs = require('fs');
var f = fs.open(FILENAME, "w");
f.write("hello");
f.writeLine("");
f.writeLine("world");
f.close();
});
test(function () {
assert_is_true(fs.exists(FILENAME));
// we might've gotten DOS line endings
assert_greater_than_equal(fs.size(FILENAME), "hello\nworld\n".length);
}, "create a file with contents");
test(function () {
assert_is_false(fs.exists(FILENAME_EMPTY));
fs.touch(FILENAME_EMPTY);
assert_is_true(fs.exists(FILENAME_EMPTY));
assert_equals(fs.size(FILENAME_EMPTY), 0);
}, "create (touch) an empty file");
test(function () {
var content = "";
var f = fs.open(FILENAME, "r");
this.add_cleanup(function () { f.close(); });
content = f.read();
assert_equals(content, "hello\nworld\n");
}, "read content from a file");
test(function () {
var content = "";
var f = fs.open(FILENAME, "r");
this.add_cleanup(function () { f.close(); });
f.seek(3);
content = f.read(5);
assert_equals(content, "lo\nwo");
}, "read specific number of bytes from a specific position in a file");
test(function () {
var content = "";
var f = fs.open(FILENAME, "rw+");
this.add_cleanup(function () { f.close(); });
f.writeLine("asdf");
content = f.read();
assert_equals(content, "hello\nworld\nasdf\n");
}, "append content to a file");
test(function () {
var f = fs.open(FILENAME, "r");
this.add_cleanup(function () { f.close(); });
assert_equals(f.getEncoding(), "UTF-8");
}, "get the file encoding (default: UTF-8)");
test(function () {
var f = fs.open(FILENAME, { charset: "UTF-8", mode: "r" });
this.add_cleanup(function () { f.close(); });
assert_equals(f.getEncoding(), "UTF-8");
var g = fs.open(FILENAME, { charset: "SJIS", mode: "r" });
this.add_cleanup(function () { g.close(); });
assert_equals(g.getEncoding(), "Shift_JIS");
}, "set the encoding on open", {/* unsupported */expected_fail: true});
test(function () {
var f = fs.open(FILENAME, { charset: "UTF-8", mode: "r" });
this.add_cleanup(function () { f.close(); });
assert_equals(f.getEncoding(), "UTF-8");
f.setEncoding("utf8");
assert_equals(f.getEncoding(), "UTF-8");
var g = fs.open(FILENAME, { charset: "SJIS", mode: "r" });
this.add_cleanup(function () { g.close(); });
assert_equals(g.getEncoding(), "Shift_JIS");
g.setEncoding("eucjp");
assert_equals(g.getEncoding(), "EUC-JP");
}, "change the encoding using setEncoding", {/* unsupported */expected_fail: true});
test(function () {
assert_is_false(fs.exists(FILENAME_COPY));
fs.copy(FILENAME, FILENAME_COPY);
assert_is_true(fs.exists(FILENAME_COPY));
assert_equals(fs.read(FILENAME), fs.read(FILENAME_COPY));
}, "copy a file");
test(function () {
assert_is_true(fs.exists(FILENAME));
var contentBeforeMove = fs.read(FILENAME);
fs.move(FILENAME, FILENAME_MOVED);
assert_is_false(fs.exists(FILENAME));
assert_is_true(fs.exists(FILENAME_MOVED));
assert_equals(fs.read(FILENAME_MOVED), contentBeforeMove);
}, "move a file");
test(function () {
assert_is_true(fs.exists(FILENAME_MOVED));
assert_is_true(fs.exists(FILENAME_COPY));
assert_is_true(fs.exists(FILENAME_EMPTY));
fs.remove(FILENAME_MOVED);
fs.remove(FILENAME_COPY);
fs.remove(FILENAME_EMPTY);
assert_is_false(fs.exists(FILENAME_MOVED));
assert_is_false(fs.exists(FILENAME_COPY));
assert_is_false(fs.exists(FILENAME_EMPTY));
}, "remove a file");
test(function () {
assert_throws("Unable to open file '"+ ABSENT +"'",
function () { fs.open(ABSENT, "r"); });
assert_throws("Unable to copy file '" + ABSENT +
"' at '" + FILENAME_COPY + "'",
function () { fs.copy(ABSENT, FILENAME_COPY); });
}, "operations on nonexistent files throw an exception", {/* unsupported */expected_fail: true});
test(function () {
var data = "ÄABCÖ";
var data_b = String.fromCharCode(
0xC3, 0x84, 0x41, 0x42, 0x43, 0xC3, 0x96);
var f = fs.open(FILENAME_ENC, "w");
this.add_cleanup(function () {
f.close();
fs.remove(FILENAME_ENC);
});
f.write(data);
f.close();
f = fs.open(FILENAME_ENC, "r");
assert_equals(f.read(), data);
var g = fs.open(FILENAME_ENC, "rb");
this.add_cleanup(function () { g.close(); });
assert_equals(g.read(), data_b);
}, "read/write UTF-8 text by default");
test(function () {
var data = "ピタゴラスイッチ";
var data_b = String.fromCharCode(
0x83, 0x73, 0x83, 0x5e, 0x83, 0x53, 0x83, 0x89,
0x83, 0x58, 0x83, 0x43, 0x83, 0x62, 0x83, 0x60);
var f = fs.open(FILENAME_ENC, { mode: "w", charset: "Shift_JIS" });
this.add_cleanup(function () {
f.close();
fs.remove(FILENAME_ENC);
});
f.write(data);
f.close();
f = fs.open(FILENAME_ENC, { mode: "r", charset: "Shift_JIS" });
assert_equals(f.read(), data);
var g = fs.open(FILENAME_ENC, "rb");
this.add_cleanup(function () { g.close(); });
assert_equals(g.read(), data_b);
}, "read/write Shift-JIS text with options", {/* unsupported */expected_fail: true});
test(function () {
var data = String.fromCharCode(0, 1, 2, 3, 4, 5);
var f = fs.open(FILENAME_BIN, "wb");
this.add_cleanup(function () {
f.close();
fs.remove(FILENAME_BIN);
});
f.write(data);
f.close();
f = fs.open(FILENAME_BIN, "rb");
assert_equals(f.read(), data);
}, "read/write binary data");
test(function () {
var data = String.fromCharCode(0, 1, 2, 3, 4, 5);
fs.write(FILENAME_BIN, data, "b");
this.add_cleanup(function () {
fs.remove(FILENAME_BIN);
});
assert_equals(fs.read(FILENAME_BIN, "b"), data);
}, "read/write binary data (shortcuts)");

View File

@ -0,0 +1,91 @@
var fs = require('fs');
var ABSENT_DIR = "absentdir02",
ABSENT_FILE = "absentfile02",
TEST_DIR = "testdir02",
TEST_FILE = "temp-02.test",
TEST_FILE_PATH = fs.join(TEST_DIR, TEST_FILE),
TEST_CONTENT = "test content",
CONTENT_MULTIPLIER = 1024;
test(function () {
assert_throws("Unable to read file '"+ ABSENT_FILE +"' size",
function () { fs.size(ABSENT_FILE); });
assert_equals(fs.lastModified(ABSENT_FILE), null);
}, "size/date queries on nonexistent files", {/* unsupported */expected_fail: true});
test(function () {
// Round down to the nearest multiple of two seconds, because
// file timestamps might only have that much precision.
var before_creation = Math.floor(Date.now() / 2000) * 2000;
var f = fs.open(TEST_FILE, "w");
this.add_cleanup(function () {
if (f !== null) f.close();
fs.remove(TEST_FILE);
});
for (var i = 0; i < CONTENT_MULTIPLIER; i++) {
f.write(TEST_CONTENT);
}
f.close(); f = null;
// Similarly, but round _up_.
var after_creation = Math.ceil(Date.now() / 2000) * 2000;
assert_equals(fs.size(TEST_FILE),
TEST_CONTENT.length * CONTENT_MULTIPLIER);
var flm = fs.lastModified(TEST_FILE).getTime();
assert_greater_than_equal(flm, before_creation);
assert_less_than_equal(flm, after_creation);
}, "size/date queries on existing files");
test(function () {
fs.makeDirectory(TEST_DIR);
this.add_cleanup(function () { fs.removeTree(TEST_DIR); });
fs.write(TEST_FILE_PATH, TEST_CONTENT, "w");
assert_is_true(fs.exists(TEST_FILE_PATH));
assert_is_true(fs.exists(TEST_DIR));
assert_is_false(fs.exists(ABSENT_FILE));
assert_is_false(fs.exists(ABSENT_DIR));
assert_is_true(fs.isDirectory(TEST_DIR));
assert_is_false(fs.isDirectory(ABSENT_DIR));
assert_is_true(fs.isFile(TEST_FILE_PATH));
assert_is_false(fs.isFile(ABSENT_FILE));
var absPath = fs.absolute(TEST_FILE_PATH);
assert_is_false(fs.isAbsolute(TEST_FILE_PATH));
assert_is_true(fs.isAbsolute(absPath));
assert_is_true(fs.isReadable(TEST_FILE_PATH));
assert_is_true(fs.isWritable(TEST_FILE_PATH));
assert_is_false(fs.isExecutable(TEST_FILE_PATH));
assert_is_false(fs.isReadable(ABSENT_FILE));
assert_is_false(fs.isWritable(ABSENT_FILE));
assert_is_false(fs.isExecutable(ABSENT_FILE));
assert_is_true(fs.isReadable(TEST_DIR));
assert_is_true(fs.isWritable(TEST_DIR));
assert_is_true(fs.isExecutable(TEST_DIR));
assert_is_false(fs.isReadable(ABSENT_DIR));
assert_is_false(fs.isWritable(ABSENT_DIR));
assert_is_false(fs.isExecutable(ABSENT_DIR));
assert_is_false(fs.isLink(TEST_DIR));
assert_is_false(fs.isLink(TEST_FILE_PATH));
assert_is_false(fs.isLink(ABSENT_DIR));
assert_is_false(fs.isLink(ABSENT_FILE));
}, "file types and access modes");

View File

@ -0,0 +1,72 @@
var fs = require('fs');
var system = require('system');
var TEST_DIR = "testdir",
TEST_FILE = "testfile",
START_CWD = fs.workingDirectory;
test(function () {
assert_is_true(fs.makeDirectory(TEST_DIR));
this.add_cleanup(function () { fs.removeTree(TEST_DIR); });
assert_is_true(fs.changeWorkingDirectory(TEST_DIR));
this.add_cleanup(function () { fs.changeWorkingDirectory(START_CWD); });
fs.write(TEST_FILE, TEST_FILE, "w");
var suffix = fs.join("", TEST_DIR, TEST_FILE),
abs = fs.absolute(".." + suffix),
lastIndex = abs.lastIndexOf(suffix);
assert_not_equals(lastIndex, -1);
assert_equals(lastIndex + suffix.length, abs.length);
}, "manipulation of current working directory");
test(function () {
fs.copyTree(phantom.libraryPath, TEST_DIR);
this.add_cleanup(function () { fs.removeTree(TEST_DIR); });
assert_deep_equals(fs.list(phantom.libraryPath), fs.list(TEST_DIR));
}, "copying a directory tree");
test(function () {
assert_type_of(fs.readLink, 'function');
// TODO: test the actual functionality once we can create symlinks.
}, "fs.readLink exists");
generate_tests(function fs_join_test (parts, expected) {
var actual = fs.join.apply(null, parts);
assert_equals(actual, expected);
}, [
[ "fs.join: []", [], "." ],
[ "fs.join: nonsense", [[], null], "." ],
[ "fs.join: 1 element", [""], "." ],
[ "fs.join: 2 elements", ["", "a"], "/a" ],
[ "fs.join: 3 elements", ["a", "b", "c"], "a/b/c" ],
[ "fs.join: 4 elements", ["", "a", "b", "c"], "/a/b/c" ],
[ "fs.join: empty elements", ["", "a", "", "b", "", "c"], "/a/b/c" ],
[ "fs.join: empty elements 2", ["a", "", "b", "", "c"], "a/b/c" ]
]);
generate_tests(function fs_split_test (input, expected) {
var path = input.join(fs.separator);
var actual = fs.split(path);
assert_deep_equals(actual, expected);
}, [
[ "fs.split: absolute",
["", "a", "b", "c", "d"], ["", "a", "b", "c", "d"] ],
[ "fs.split: absolute, trailing",
["", "a", "b", "c", "d", ""], ["", "a", "b", "c", "d"] ],
[ "fs.split: non-absolute",
["a", "b", "c", "d"], ["a", "b", "c", "d"] ],
[ "fs.split: non-absolute, trailing",
["a", "b", "c", "d", ""], ["a", "b", "c", "d"] ],
[ "fs.split: repeated separators",
["a", "", "", "",
"b", "",
"c", "", "",
"d", "", "", ""], ["a", "b", "c", "d"] ]
]);

View File

@ -0,0 +1,22 @@
//! stdin: Victor jagt zwölf Boxkämpfer quer über den großen Sylter Deich
//! stdin: いろはにほへとちりぬるをわかよたれそつねならむうゐのおくやまけふこえてあさきゆめみしゑひもせす
//^ first line: pangram in German
//^ second line: pan+isogram in hiragana (the Iroha)
var stdin;
setup(function () { stdin = require("system").stdin; });
test(function () {
assert_equals(stdin.readLine(),
"Victor jagt zwölf Boxkämpfer quer über den großen Sylter Deich");
}, "input line one (German)");
test(function () {
assert_equals(stdin.readLine(),
"いろはにほへとちりぬるをわかよたれそつねならむうゐのおくやまけふこえてあさきゆめみしゑひもせす");
}, "input line two (Japanese)");
test(function () {
assert_equals(stdin.readLine(), "");
}, "input line three (EOF)");

View File

@ -0,0 +1,14 @@
//! no-harness
//! expect-stdout: Victor jagt zwölf Boxkämpfer quer über den großen Sylter Deich
//! expect-stderr: いろはにほへとちりぬるをわかよたれそつねならむうゐのおくやまけふこえてあさきゆめみしゑひもせす
//^ stdout: pangram in German
//^ stderr: pan+isogram in hiragana (the Iroha)
phantom.onError = function () { phantom.exit(1); };
var sys = require("system");
sys.stdout.write("Victor jagt zwölf Boxkämpfer quer über den großen Sylter Deich\n");
sys.stderr.write("いろはにほへとちりぬるをわかよたれそつねならむうゐのおくやまけふこえてあさきゆめみしゑひもせす");
phantom.exit(0);

View File

@ -0,0 +1,77 @@
var system = require('system');
test(function () {
assert_type_of(system, 'object');
assert_not_equals(system, null);
}, "system object");
test(function () {
assert_own_property(system, 'pid');
assert_type_of(system.pid, 'number');
assert_greater_than(system.pid, 0);
}, "system.pid");
test(function () {
assert_own_property(system, 'isSSLSupported');
assert_type_of(system.isSSLSupported, 'boolean');
assert_equals(system.isSSLSupported, true);
}, "system.isSSLSupported", {/* unsupported */expected_fail: true});
test(function () {
assert_own_property(system, 'args');
assert_type_of(system.args, 'object');
assert_instance_of(system.args, Array);
assert_greater_than_equal(system.args.length, 1);
// args[0] will be the test harness.
assert_regexp_match(system.args[0], /\btestharness\.js$/);
}, "system.args", {/* unsupported */expected_fail: true});
test(function () {
assert_own_property(system, 'env');
assert_type_of(system.env, 'object');
}, "system.env");
test(function () {
assert_own_property(system, 'platform');
assert_type_of(system.platform, 'string');
assert_equals(system.platform, 'phantomjs');
}, "system.platform");
test(function () {
assert_own_property(system, 'os');
assert_type_of(system.os, 'object');
assert_type_of(system.os.architecture, 'string');
assert_type_of(system.os.name, 'string');
assert_type_of(system.os.version, 'string');
if (system.os.name === 'mac') {
// release is x.y.z with x = 10 for Snow Leopard and 14 for Yosemite
assert_type_of(system.os.release, 'string');
assert_greater_than_equal(parseInt(system.os.release, 10), 10);
}
}, "system.os");
test(function () {
assert_type_of(system.stdin, 'object');
assert_type_of(system.stdin.read, 'function');
assert_type_of(system.stdin.readLine, 'function');
assert_type_of(system.stdin.close, 'function');
}, "system.stdin");
test(function () {
assert_type_of(system.stdout, 'object');
assert_type_of(system.stdout.write, 'function');
assert_type_of(system.stdout.writeLine, 'function');
assert_type_of(system.stdout.flush, 'function');
assert_type_of(system.stdout.close, 'function');
}, "system.stdout");
test(function () {
assert_type_of(system.stderr, 'object');
assert_type_of(system.stderr.write, 'function');
assert_type_of(system.stderr.writeLine, 'function');
assert_type_of(system.stderr.flush, 'function');
assert_type_of(system.stderr.close, 'function');
}, "system.stderr");

View File

@ -0,0 +1,37 @@
//! unsupported
var webpage = require('webpage');
async_test(function () {
var page = webpage.create();
var abortCount = 0;
var errorCount = 0;
var abortedIds = {};
var urlToBlockRegExp = /logo\.png$/i;
page.onResourceRequested = this.step_func(function(requestData, request) {
assert_type_of(request, 'object');
assert_type_of(request.abort, 'function');
if (urlToBlockRegExp.test(requestData.url)) {
request.abort();
++abortCount;
abortedIds[requestData.id] = 1;
}
});
page.onResourceError = this.step_func(function(error) {
// We can't match up errors to requests by URL because error.url will
// be the empty string in this case. FIXME.
assert_own_property(abortedIds, error.id);
++errorCount;
});
page.onResourceReceived = this.step_func(function(response) {
assert_regexp_not_match(response.url, urlToBlockRegExp);
});
page.open(TEST_HTTP_BASE + 'logo.html',
this.step_func_done(function (status) {
assert_equals(status, 'success');
assert_equals(abortCount, 1);
assert_equals(errorCount, 1);
}));
}, "can abort network requests");

View File

@ -0,0 +1,25 @@
//! unsupported
async_test(function () {
var webpage = require('webpage');
// NOTE: HTTP header names are case-insensitive. Our test server
// returns the name in lowercase.
var page = webpage.create();
assert_type_of(page.customHeaders, 'object');
assert_deep_equals(page.customHeaders, {});
page.onResourceRequested = this.step_func(function(requestData, request) {
assert_type_of(request.setHeader, 'function');
request.setHeader('CustomHeader', 'CustomValue');
});
page.open(TEST_HTTP_BASE + 'echo', this.step_func_done(function (status) {
var json, headers;
assert_equals(status, 'success');
json = JSON.parse(page.plainText);
headers = json.headers;
assert_own_property(headers, 'customheader');
assert_equals(headers.customheader, 'CustomValue');
}));
}, "add custom headers in onResourceRequested");

View File

@ -0,0 +1,16 @@
test(function () {
var page = require('webpage').create();
var msgA = "a",
msgB = "b",
result,
expected = msgA + msgB;
page.onCallback = function(a, b) {
return a + b;
};
result = page.evaluate(function(a, b) {
return window.callPhantom(a, b);
}, msgA, msgB);
assert_equals(result, expected);
}, "page.onCallback");

View File

@ -0,0 +1,50 @@
//! unsupported
var content;
setup(function () {
var fs = require('fs');
// libraryPath is test/module/webpage
content = fs.read(fs.join(phantom.libraryPath,
"../../www/hello.html"));
});
// XFAIL: This feature had to be backed out for breaking WebSockets.
async_test(function () {
var page = require('webpage').create();
var lastChunk = "";
var bodySize = 0;
page.captureContent = ['.*'];
// Not a step function because it may be called several times
// and doesn't need to make assertions.
page.onResourceReceived = function (resource) {
lastChunk = resource.body;
bodySize = resource.bodySize;
};
page.open(TEST_HTTP_BASE + "hello.html",
this.step_func_done(function (status) {
assert_equals(status, "success");
assert_equals(bodySize, content.length);
assert_equals(lastChunk, content);
}));
}, "onResourceReceived sees the body if captureContent is activated",
{ expected_fail: true }
);
async_test(function () {
var page = require('webpage').create();
var lastChunk = "";
var bodySize = 0;
page.captureContent = ['/some/other/url'];
// Not a step function because it may be called several times
// and doesn't need to make assertions.
page.onResourceReceived = function (resource) {
lastChunk = resource.body;
bodySize = resource.bodySize;
};
page.open(TEST_HTTP_BASE + "hello.html",
this.step_func_done(function (status) {
assert_equals(status, "success");
assert_equals(bodySize, 0);
assert_equals(lastChunk, "");
}));
}, "onResourceReceived doesn't see the body if captureContent doesn't match");

View File

@ -0,0 +1,27 @@
//! unsupported
var webpage = require('webpage');
async_test(function () {
var page = webpage.create();
var url = TEST_HTTP_BASE + "cdn-cgi/pe/bag?r%5B%5D="+
"http%3A%2F%2Fwww.example.org%2Fcdn-cgi%2Fnexp%2F"+
"abv%3D927102467%2Fapps%2Fabetterbrowser.js";
var receivedUrl;
page.onResourceRequested = this.step_func(function(requestData, request) {
request.changeUrl(requestData.url);
});
page.onResourceReceived = this.step_func(function(data) {
if (data.stage === 'end') {
receivedUrl = data.url;
}
});
page.open(url, this.step_func_done(function (status) {
assert_equals(status, 'success');
assert_equals(receivedUrl, url);
}));
}, "encoded URLs properly round-trip through request.changeUrl");

View File

@ -0,0 +1,40 @@
//! unsupported
var webpage = require('webpage');
async_test(function () {
var page = webpage.create();
var urlToChange = TEST_HTTP_BASE + 'logo.png';
var alternativeUrl = TEST_HTTP_BASE + 'phantomjs-logo.gif';
var startStage = 0;
var endStage = 0;
page.onResourceRequested = this.step_func(function(requestData, request) {
if (requestData.url === urlToChange) {
assert_type_of(request, 'object');
assert_type_of(request.changeUrl, 'function');
request.changeUrl(alternativeUrl);
}
});
page.onResourceReceived = this.step_func(function(data) {
if (data.url === alternativeUrl && data.stage === 'start') {
++startStage;
}
if (data.url === alternativeUrl && data.stage === 'end') {
++endStage;
}
});
page.open(TEST_HTTP_BASE + 'logo.html',
this.step_func_done(function (status) {
assert_equals(status, 'success');
assert_equals(startStage, 1);
assert_equals(endStage, 1);
// The page HTML should still refer to the original image.
assert_regexp_match(page.content, /logo\.png/);
assert_regexp_not_match(page.content, /logo\.gif/);
}));
}, "request.changeUrl");

View File

@ -0,0 +1,33 @@
var webpage = require('webpage');
function test_one(text) {
var t = async_test(text.codec);
t.step(function () {
var page = webpage.create();
page.open(text.url, t.step_func_done(function () {
var decodedText = page.evaluate(function() {
return document.querySelector('pre').innerText;
});
var regex = '^' + text.reference + '$';
assert_regexp_match(text.reference, new RegExp(regex));
}));
});
}
function Text(codec, base64, reference) {
this.codec = codec;
this.base64 = base64;
this.reference = reference;
this.url = 'data:text/plain;charset=' + this.codec +
';base64,' + this.base64;
}
[
new Text('Shift_JIS', 'g3SDQIOTg2eDgA==', 'ファントム'),
new Text('EUC-JP', 'pdWloaXzpcil4A0K', 'ファントム'),
new Text('ISO-2022-JP', 'GyRCJVUlISVzJUglYBsoQg0K', 'ファントム'),
new Text('Big5', 'pNu2SA0K', '幻象'),
new Text('GBK', 'u8PP8w0K', '幻象'),
new Text('EUC-KR', 'yK+/tQ==', '환영'),
]
.forEach(test_one);

View File

@ -0,0 +1,19 @@
var webpage = require('webpage');
test(function () {
var defaultPage = webpage.create();
assert_deep_equals(defaultPage.clipRect, {height:0,left:0,top:0,width:0});
}, "default page.clipRect");
test(function () {
var options = {
clipRect: {
height: 100,
left: 10,
top: 20,
width: 200
}
};
var customPage = webpage.create(options);
assert_deep_equals(customPage.clipRect, options.clipRect);
}, "custom page.clipRect");

View File

@ -0,0 +1,59 @@
//! unsupported
test(function () {
var opts = {},
page = new WebPage(opts);
assert_type_of(page, 'object');
assert_not_equals(page, null);
}, "webpage constructor accepts an opts object");
async_test(function () {
var opts = {
onConsoleMessage: this.step_func_done(function (msg) {
assert_equals(msg, "test log");
})
};
var page = new WebPage(opts);
assert_equals(page.onConsoleMessage, opts.onConsoleMessage);
page.evaluate(function () {console.log('test log');});
}, "specifying onConsoleMessage with opts");
async_test(function () {
var page_opened = false;
var opts = {
onLoadStarted: this.step_func_done(function (msg) {
assert_is_true(page_opened);
})
};
var page = new WebPage(opts);
assert_equals(page.onLoadStarted, opts.onLoadStarted);
page_opened = true;
page.open("about:blank");
}, "specifying onLoadStarted with opts");
async_test(function () {
var page_opened = false;
var opts = {
onLoadFinished: this.step_func_done(function (msg) {
assert_is_true(page_opened);
})
};
var page = new WebPage(opts);
assert_equals(page.onLoadFinished, opts.onLoadFinished);
page_opened = true;
page.open("about:blank");
}, "specifying onLoadFinished with opts");
// FIXME: Actually test that the timeout is effective.
test(function () {
var opts = {
settings: {
timeout: 100 // time in ms
}
};
var page = new WebPage(opts);
assert_equals(page.settings.timeout, opts.settings.timeout);
}, "specifying timeout with opts");

View File

@ -0,0 +1,33 @@
//! unsupported
test(function () {
var page = require('webpage').create();
page.evaluate(function() {
window.addEventListener('contextmenu', function(event) {
window.loggedEvent = window.loggedEvent || {};
window.loggedEvent.contextmenu = event;
}, false);
});
page.sendEvent('contextmenu', 42, 217);
var event = page.evaluate(function() {
return window.loggedEvent;
});
assert_equals(event.contextmenu.clientX, 42);
assert_equals(event.contextmenu.clientY, 217);
// click with modifier key
page.evaluate(function() {
window.addEventListener('contextmenu', function(event) {
window.loggedEvent = window.loggedEvent || {};
window.loggedEvent.contextmenu = event;
}, false);
});
page.sendEvent('contextmenu', 100, 100, 'left', page.event.modifier.shift);
var event = page.evaluate(function() {
return window.loggedEvent.contextmenu;
});
assert_is_true(event.shiftKey);
}, "context click events");

View File

@ -0,0 +1,112 @@
//! unsupported
async_test(function () {
var url = TEST_HTTP_BASE + "echo";
var page = new WebPage();
page.cookies = [{
'name' : 'Valid-Cookie-Name',
'value' : 'Valid-Cookie-Value',
'domain' : 'localhost',
'path' : '/',
'httponly' : true,
'secure' : false
},{
'name' : 'Valid-Cookie-Name-Sec',
'value' : 'Valid-Cookie-Value-Sec',
'domain' : 'localhost',
'path' : '/',
'httponly' : true,
'secure' : false,
'expires' : Date.now() + 3600 //< expires in 1h
}];
page.open(url, this.step_func(function (status) {
assert_equals(status, "success");
var headers = JSON.parse(page.plainText).headers;
assert_own_property(headers, 'cookie');
assert_regexp_match(headers.cookie, /\bValid-Cookie-Name\b/);
assert_regexp_match(headers.cookie, /\bValid-Cookie-Value\b/);
assert_regexp_match(headers.cookie, /\bValid-Cookie-Name-Sec\b/);
assert_regexp_match(headers.cookie, /\bValid-Cookie-Value-Sec\b/);
assert_not_equals(page.cookies.length, 0);
page.cookies = [];
page.open(url, this.step_func_done(function (status) {
assert_equals(status, "success");
var headers = JSON.parse(page.plainText).headers;
assert_no_property(headers, 'cookie');
}));
}));
}, "adding and deleting cookies with page.cookies");
async_test(function () {
var url = TEST_HTTP_BASE + "echo";
var page = new WebPage();
page.addCookie({
'name' : 'Added-Cookie-Name',
'value' : 'Added-Cookie-Value',
'domain' : 'localhost'
});
page.open(url, this.step_func(function (status) {
assert_equals(status, "success");
var headers = JSON.parse(page.plainText).headers;
assert_own_property(headers, 'cookie');
assert_regexp_match(headers.cookie, /\bAdded-Cookie-Name\b/);
assert_regexp_match(headers.cookie, /\bAdded-Cookie-Value\b/);
page.deleteCookie("Added-Cookie-Name");
page.open(url, this.step_func_done(function (status) {
assert_equals(status, "success");
var headers = JSON.parse(page.plainText).headers;
assert_no_property(headers, 'cookie');
}));
}));
}, "adding and deleting cookies with page.addCookie and page.deleteCookie");
async_test(function () {
var url = TEST_HTTP_BASE + "echo";
var page = new WebPage();
page.cookies = [
{ // domain mismatch.
'name' : 'Invalid-Cookie-Name-1',
'value' : 'Invalid-Cookie-Value-1',
'domain' : 'foo.example'
},{ // path mismatch: the cookie will be set,
// but won't be visible from the given URL (not same path).
'name' : 'Invalid-Cookie-Name-2',
'value' : 'Invalid-Cookie-Value-2',
'domain' : 'localhost',
'path' : '/bar'
},{ // cookie expired.
'name' : 'Invalid-Cookie-Name-3',
'value' : 'Invalid-Cookie-Value-3',
'domain' : 'localhost',
'expires' : 'Sat, 01 Jan 2000 00:00:00 GMT'
},{ // https only: the cookie will be set,
// but won't be visible from the given URL (not https).
'name' : 'Invalid-Cookie-Name-4',
'value' : 'Invalid-Cookie-Value-4',
'domain' : 'localhost',
'secure' : true
},{ // cookie expired (date in "sec since epoch").
'name' : 'Invalid-Cookie-Name-5',
'value' : 'Invalid-Cookie-Value-5',
'domain' : 'localhost',
'expires' : new Date().getTime() - 1 //< date in the past
},{ // cookie expired (date in "sec since epoch" - using "expiry").
'name' : 'Invalid-Cookie-Name-6',
'value' : 'Invalid-Cookie-Value-6',
'domain' : 'localhost',
'expiry' : new Date().getTime() - 1 //< date in the past
}];
page.open(url, this.step_func_done(function (status) {
assert_equals(status, "success");
var headers = JSON.parse(page.plainText).headers;
assert_no_property(headers, 'cookie');
}));
}, "page.cookies provides cookies only to appropriate requests");

View File

@ -0,0 +1,30 @@
async_test(function () {
var webpage = require('webpage');
var page = webpage.create();
assert_type_of(page.customHeaders, 'object');
assert_deep_equals(page.customHeaders, {});
// NOTE: HTTP header names are case-insensitive. Our test server
// returns the name in lowercase.
page.customHeaders = {
'Custom-Key': 'Custom-Value',
'User-Agent': 'Overriden-UA',
'Referer': 'http://example.com/'
};
page.open(TEST_HTTP_BASE + 'echo', this.step_func_done(function (status) {
var json, headers;
assert_equals(status, 'success');
json = JSON.parse(page.plainText);
assert_type_of(json, 'object');
headers = json.headers;
assert_type_of(headers, 'object');
assert_own_property(headers, 'custom-key');
assert_own_property(headers, 'user-agent');
assert_own_property(headers, 'referer');
assert_equals(headers['custom-key'], 'Custom-Value');
assert_equals(headers['user-agent'], 'Overriden-UA');
assert_equals(headers['referer'], 'http://example.com/');
}));
}, "adding custom headers with page.customHeaders");

View File

@ -0,0 +1,14 @@
test(function () {
var webpage = require('webpage');
var page = webpage.create();
// Hijack JSON.parse to something completely useless.
page.content = '<html><script>JSON.parse = function() {}</script></html>';
var result = page.evaluate(function(obj) {
return obj.value * obj.value;
}, { value: 4 });
assert_equals(result, 16);
}, "page script should not interfere with page.evaluate");

View File

@ -0,0 +1,56 @@
//! unsupported
// Note: uses various files in module/webpage as things to be uploaded.
// Which files they are doesn't matter.
var page;
setup(function () {
page = new WebPage();
page.content =
'<input type="file" id="file">\n' +
'<input type="file" id="file2" multiple>\n' +
'<input type="file" id="file3" multiple>' +
'<input type="file" id="file4">';
page.uploadFile("#file", "file-upload.js");
page.uploadFile("#file2", "file-upload.js");
page.uploadFile("#file3", ["file-upload.js", "object.js"]);
});
function test_one_elt(id, names) {
var files = page.evaluate(function (id) {
var elt = document.getElementById(id);
var rv = [];
for (var i = 0; i < elt.files.length; i++) {
rv.push(elt.files[i].fileName);
}
return rv;
}, id);
assert_deep_equals(files, names);
}
generate_tests(test_one_elt, [
["single upload single file", "file", ["file-upload.js"]],
["multiple upload single file", "file2", ["file-upload.js"]],
["multiple upload multiple file", "file3", ["file-upload.js", "object.js"]],
], { expected_fail: true });
async_test(function () {
page.onFilePicker = this.step_func(function (oldFile) {
assert_equals(oldFile, "");
return "no-plugin.js";
});
test_one_elt("file4", []);
page.evaluate(function () {
var fileUp = document.querySelector("#file4");
var ev = document.createEvent("MouseEvents");
ev.initEvent("click", true, true);
fileUp.dispatchEvent(ev);
});
setTimeout(this.step_func_done(function () {
test_one_elt("file4", ["no-plugin.js"]);
}, 0));
}, "page.onFilePicker", { expected_fail: true });

View File

@ -0,0 +1,69 @@
//! unsupported
async_test(function () {
var p = require("webpage").create();
function pageTitle(page) {
return page.evaluate(function(){
return window.document.title;
});
}
function setPageTitle(page, newTitle) {
page.evaluate(function(newTitle){
window.document.title = newTitle;
}, newTitle);
}
function testFrameSwitchingDeprecated() {
assert_equals(pageTitle(p), "index");
assert_equals(p.currentFrameName(), "");
assert_equals(p.childFramesCount(), 2);
assert_deep_equals(p.childFramesName(), ["frame1", "frame2"]);
setPageTitle(p, pageTitle(p) + "-visited");
assert_is_true(p.switchToChildFrame("frame1"));
assert_equals(pageTitle(p), "frame1");
assert_equals(p.currentFrameName(), "frame1");
assert_equals(p.childFramesCount(), 2);
assert_deep_equals(p.childFramesName(), ["frame1-1", "frame1-2"]);
setPageTitle(p, pageTitle(p) + "-visited");
assert_is_true(p.switchToChildFrame("frame1-2"));
assert_equals(pageTitle(p), "frame1-2");
assert_equals(p.currentFrameName(), "frame1-2");
assert_equals(p.childFramesCount(), 0);
assert_deep_equals(p.childFramesName(), []);
setPageTitle(p, pageTitle(p) + "-visited");
assert_is_true(p.switchToParentFrame());
assert_equals(pageTitle(p), "frame1-visited");
assert_equals(p.currentFrameName(), "frame1");
assert_equals(p.childFramesCount(), 2);
assert_deep_equals(p.childFramesName(), ["frame1-1", "frame1-2"]);
assert_is_true(p.switchToChildFrame(0));
assert_equals(pageTitle(p), "frame1-1");
assert_equals(p.currentFrameName(), "frame1-1");
assert_equals(p.childFramesCount(), 0);
assert_deep_equals(p.childFramesName(), []);
assert_equals(p.switchToMainFrame(), undefined);
assert_equals(pageTitle(p), "index-visited");
assert_equals(p.currentFrameName(), "");
assert_equals(p.childFramesCount(), 2);
assert_deep_equals(p.childFramesName(), ["frame1", "frame2"]);
assert_is_true(p.switchToChildFrame("frame2"));
assert_equals(pageTitle(p), "frame2");
assert_equals(p.currentFrameName(), "frame2");
assert_equals(p.childFramesCount(), 3);
assert_deep_equals(p.childFramesName(),
["frame2-1", "frame2-2", "frame2-3"]);
}
p.open(TEST_HTTP_BASE + "frameset", this.step_func_done(function (s) {
assert_equals(s, "success");
testFrameSwitchingDeprecated();
}));
}, "frame switching deprecated API");

View File

@ -0,0 +1,98 @@
//! unsupported
async_test(function () {
var p = require("webpage").create();
function pageTitle(page) {
return page.evaluate(function(){
return window.document.title;
});
}
function setPageTitle(page, newTitle) {
page.evaluate(function(newTitle){
window.document.title = newTitle;
}, newTitle);
}
function testFrameSwitching() {
assert_equals(pageTitle(p), "index");
assert_equals(p.frameName, "");
assert_equals(p.framesCount, 2);
assert_deep_equals(p.framesName, ["frame1", "frame2"]);
setPageTitle(p, pageTitle(p) + "-visited");
assert_is_true(p.switchToFrame("frame1"));
assert_equals(pageTitle(p), "frame1");
assert_equals(p.frameName, "frame1");
assert_equals(p.framesCount, 2);
assert_deep_equals(p.framesName, ["frame1-1", "frame1-2"]);
setPageTitle(p, pageTitle(p) + "-visited");
assert_is_true(p.switchToFrame("frame1-2"));
assert_equals(pageTitle(p), "frame1-2");
assert_equals(p.frameName, "frame1-2");
assert_equals(p.framesCount, 0);
assert_deep_equals(p.framesName, []);
setPageTitle(p, pageTitle(p) + "-visited");
assert_is_true(p.switchToParentFrame());
assert_equals(pageTitle(p), "frame1-visited");
assert_equals(p.frameName, "frame1");
assert_equals(p.framesCount, 2);
assert_deep_equals(p.framesName, ["frame1-1", "frame1-2"]);
assert_is_true(p.switchToFrame(0));
assert_equals(pageTitle(p), "frame1-1");
assert_equals(p.frameName, "frame1-1");
assert_equals(p.framesCount, 0);
assert_deep_equals(p.framesName, []);
assert_equals(p.switchToMainFrame(), undefined);
assert_equals(pageTitle(p), "index-visited");
assert_equals(p.frameName, "");
assert_equals(p.framesCount, 2);
assert_deep_equals(p.framesName, ["frame1", "frame2"]);
assert_is_true(p.switchToFrame("frame2"));
assert_equals(pageTitle(p), "frame2");
assert_equals(p.frameName, "frame2");
assert_equals(p.framesCount, 3);
assert_deep_equals(p.framesName,
["frame2-1", "frame2-2", "frame2-3"]);
assert_equals(p.focusedFrameName, "");
p.evaluate(function(){
window.focus();
});
assert_equals(p.focusedFrameName, "frame2");
assert_is_true(p.switchToFrame("frame2-1"));
p.evaluate(function(){
window.focus();
});
assert_equals(p.focusedFrameName, "frame2-1");
assert_equals(p.switchToMainFrame(), undefined);
p.evaluate(function(){
window.focus();
});
assert_equals(p.focusedFrameName, "");
p.evaluate(function(){
window.frames[0].focus();
});
assert_equals(p.focusedFrameName, "frame1");
assert_equals(p.frameName, "");
assert_equals(p.switchToFocusedFrame(), undefined);
assert_equals(p.frameName, "frame1");
}
p.open(TEST_HTTP_BASE + "frameset",
this.step_func_done(function (s) {
assert_equals(s, "success");
testFrameSwitching();
}));
}, "frame switching API");

View File

@ -0,0 +1,15 @@
async_test(function () {
// This loads the same page as https-good-cert.js, but does not
// tell PhantomJS to trust the snakeoil certificate that the test
// HTTPS server uses, so it should fail.
var page = require('webpage').create();
var url = TEST_HTTPS_BASE;
page.onResourceError = this.step_func(function (err) {
assert_equals(err.url, url);
assert_equals(err.errorString, "SSL handshake failed");
});
page.open(url, this.step_func_done(function (status) {
assert_not_equals(status, "success");
}));
}, "should fail to load an HTTPS webpage with a self-signed certificate");

View File

@ -0,0 +1,14 @@
//! unsupported
//! snakeoil
async_test(function () {
// This loads the same page as https-bad-cert.js, but tells
// PhantomJS to trust the snakeoil certificate
// that the test HTTPS server uses, so it should succeed.
var page = require('webpage').create();
var url = TEST_HTTPS_BASE;
page.onResourceError = this.unreached_func();
page.open(url, this.step_func_done(function (status) {
assert_equals(status, "success");
}));
}, "loading an HTTPS webpage");

View File

@ -0,0 +1,42 @@
var webpage = require('webpage');
async_test(function () {
var page = webpage.create();
page.open(TEST_HTTP_BASE + 'includejs1.html',
this.step_func(function (status) {
assert_equals(status, 'success');
page.includeJs(TEST_HTTP_BASE + 'includejs.js',
this.step_func_done(function () {
var title = page.evaluate('getTitle');
assert_equals(title, 'i am includejs one');
}));
}));
}, "including JS in a page");
async_test(function () {
var page = webpage.create();
var already = false;
page.open(TEST_HTTP_BASE + 'includejs1.html',
this.step_func(function (status) {
assert_equals(status, 'success');
page.includeJs(TEST_HTTP_BASE + 'includejs.js',
this.step_func(function () {
assert_is_false(already);
already = true;
var title = page.evaluate('getTitle');
assert_equals(title, 'i am includejs one');
page.open(TEST_HTTP_BASE + 'includejs2.html',
this.step_func(function (status) {
assert_equals(status, 'success');
page.includeJs(TEST_HTTP_BASE + 'includejs.js',
this.step_func_done(function () {
assert_is_true(already);
var title = page.evaluate('getTitle');
assert_equals(title, 'i am includejs two');
}));
}));
}));
}));
}, "after-inclusion callbacks should fire only once");

View File

@ -0,0 +1,21 @@
//! unsupported
test(function () {
var webpage = require('webpage');
var page = webpage.create();
page.evaluate(function() {
window.addEventListener('keydown', function(event) {
window.loggedEvent = window.loggedEvent || [];
window.loggedEvent.push(event);
}, false);
});
page.sendEvent('keydown', page.event.key.A);
var loggedEvent = page.evaluate(function() {
return window.loggedEvent;
});
assert_equals(loggedEvent.length, 1);
assert_equals(loggedEvent[0].which, page.event.key.A);
}, "key-down events");

View File

@ -0,0 +1,66 @@
//! unsupported
test(function () {
var webpage = require('webpage');
var page = webpage.create();
page.evaluate(function() {
window.addEventListener('keypress', function(event) {
window.loggedEvent = window.loggedEvent || [];
window.loggedEvent.push(event);
}, false);
});
page.sendEvent('keypress', page.event.key.C);
var loggedEvent = page.evaluate(function() {
return window.loggedEvent;
});
assert_equals(loggedEvent.length, 1);
assert_equals(loggedEvent[0].which, page.event.key.C);
// Send keypress events to an input element and observe the effect.
page.content = '<input type="text">';
page.evaluate(function() {
document.querySelector('input').focus();
});
function getText() {
return page.evaluate(function() {
return document.querySelector('input').value;
});
}
page.sendEvent('keypress', page.event.key.A);
assert_equals(getText(), 'A');
page.sendEvent('keypress', page.event.key.B);
assert_equals(getText(), 'AB');
page.sendEvent('keypress', page.event.key.Backspace);
assert_equals(getText(), 'A');
page.sendEvent('keypress', page.event.key.Backspace);
assert_equals(getText(), '');
page.sendEvent('keypress', 'XYZ');
assert_equals(getText(), 'XYZ');
// Special character: A with umlaut
page.sendEvent('keypress', 'ä');
assert_equals(getText(), 'XYZä');
// 0x02000000 is the Shift modifier.
page.sendEvent('keypress', page.event.key.Home, null, null, 0x02000000);
page.sendEvent('keypress', page.event.key.Delete);
assert_equals(getText(), '');
// Cut and Paste
// 0x04000000 is the Control modifier.
page.sendEvent('keypress', 'ABCD');
assert_equals(getText(), 'ABCD');
page.sendEvent('keypress', page.event.key.Home, null, null, 0x02000000);
page.sendEvent('keypress', 'x', null, null, 0x04000000);
assert_equals(getText(), '');
page.sendEvent('keypress', 'v', null, null, 0x04000000);
assert_equals(getText(), 'ABCD');
}, "key press events");

View File

@ -0,0 +1,21 @@
//! unsupported
test(function () {
var webpage = require('webpage');
var page = webpage.create();
page.evaluate(function() {
window.addEventListener('keyup', function(event) {
window.loggedEvent = window.loggedEvent || [];
window.loggedEvent.push(event);
}, false);
});
page.sendEvent('keyup', page.event.key.B);
var loggedEvent = page.evaluate(function() {
return window.loggedEvent;
});
assert_equals(loggedEvent.length, 1);
assert_equals(loggedEvent[0].which, page.event.key.B);
}, "key-up events");

View File

@ -0,0 +1,22 @@
//! unsupported
async_test(function () {
var webpage = require('webpage');
var page = webpage.create();
assert_type_of(page, 'object');
assert_type_of(page.loading, 'boolean');
assert_type_of(page.loadingProgress, 'number');
assert_is_false(page.loading);
assert_equals(page.loadingProgress, 0);
page.open(TEST_HTTP_BASE + 'hello.html',
this.step_func_done(function (status) {
assert_equals(status, 'success');
assert_equals(page.loading, false);
assert_equals(page.loadingProgress, 100);
}));
assert_is_true(page.loading);
assert_greater_than(page.loadingProgress, 0);
}, "page loading progress");

View File

@ -0,0 +1,23 @@
//! unsupported
//! phantomjs: --web-security=no --local-url-access=no
var webpage = require("webpage");
async_test(function () {
var page = webpage.create();
var url = TEST_HTTP_BASE + "iframe.html#file:///nonexistent";
var rsErrorCalled = false;
page.onResourceError = this.step_func(function (error) {
rsErrorCalled = true;
assert_equals(error.url, "file:///nonexistent");
assert_equals(error.errorCode, 301);
assert_equals(error.errorString, 'Protocol "file" is unknown');
});
page.open(url, this.step_func_done(function () {
assert_is_true(rsErrorCalled);
}));
},
"doesn't attempt to load a file: URL in an iframe with --local-url-access=no");

View File

@ -0,0 +1,22 @@
//! unsupported
//! phantomjs: --local-url-access=no
var webpage = require("webpage");
async_test(function () {
var page = webpage.create();
var url = "file:///nonexistent";
var rsErrorCalled = false;
page.onResourceError = this.step_func(function (error) {
rsErrorCalled = true;
assert_equals(error.url, url);
assert_equals(error.errorCode, 301);
assert_equals(error.errorString, 'Protocol "file" is unknown');
});
page.open(url, this.step_func_done(function () {
assert_is_true(rsErrorCalled);
}));
}, "doesn't attempt to load a file: URL with --local-url-access=no");

View File

@ -0,0 +1,23 @@
//! unsupported
//! phantomjs: --web-security=no --local-url-access=yes
var webpage = require("webpage");
async_test(function () {
var page = webpage.create();
var url = TEST_HTTP_BASE + "iframe.html#file:///nonexistent";
var rsErrorCalled = false;
page.onResourceError = this.step_func(function (error) {
rsErrorCalled = true;
assert_equals(error.url, "file:///nonexistent");
assert_equals(error.errorCode, 203);
assert_regexp_match(error.errorString,
/^Error opening\b.*?\bnonexistent:/);
});
page.open(url, this.step_func_done(function () {
assert_is_true(rsErrorCalled);
}));
}, "attempts to load a file: URL in an iframe with --local-url-access=yes");

View File

@ -0,0 +1,23 @@
//! unsupported
//! phantomjs: --local-url-access=yes
var webpage = require("webpage");
async_test(function () {
var page = webpage.create();
var url = "file:///nonexistent";
var rsErrorCalled = false;
page.onResourceError = this.step_func(function (error) {
rsErrorCalled = true;
assert_equals(error.url, url);
assert_equals(error.errorCode, 203);
assert_regexp_match(error.errorString,
/^Error opening\b.*?\bnonexistent:/);
});
page.open(url, this.step_func_done(function () {
assert_is_true(rsErrorCalled);
}));
}, "attempts to load a file: URL with --local-url-access=yes");

View File

@ -0,0 +1,19 @@
//! unsupported
async_test(function () {
var page = require('webpage').create();
page.onLongRunningScript = this.step_func_done(function () {
page.stopJavaScript();
});
page.open(TEST_HTTP_BASE + "js-infinite-loop.html",
this.step_func(function (s) {
assert_equals(s, "success");
}));
}, "page.onLongRunningScript can interrupt scripts", {
skip: true // https://github.com/ariya/phantomjs/issues/13490
// The underlying WebKit feature is so broken that an
// infinite loop in a _page_ script prevents timeouts
// from firing in the _controller_!
});

View File

@ -0,0 +1,28 @@
//! unsupported
async_test(function () {
var webpage = require('webpage');
// NOTE: HTTP header names are case-insensitive. Our test server
// returns the name in lowercase.
var page = webpage.create();
assert_type_of(page.customHeaders, 'object');
assert_deep_equals(page.customHeaders, {});
page.customHeaders = { 'CustomHeader': 'CustomValue' };
page.onResourceRequested = this.step_func(function(requestData, request) {
assert_type_of(request.setHeader, 'function');
request.setHeader('CustomHeader', 'ModifiedCustomValue');
});
page.open(TEST_HTTP_BASE + 'echo', this.step_func_done(function (status) {
var json, headers;
assert_equals(status, 'success');
json = JSON.parse(page.plainText);
headers = json.headers;
assert_own_property(headers, 'customheader');
assert_equals(headers.customheader, 'ModifiedCustomValue');
}));
}, "modifying HTTP headers");

View File

@ -0,0 +1,39 @@
//! unsupported
test(function () {
var page = require('webpage').create();
page.evaluate(function() {
window.addEventListener('mousedown', function(event) {
window.loggedEvent = window.loggedEvent || {};
window.loggedEvent.mousedown = event;
}, false);
window.addEventListener('mouseup', function(event) {
window.loggedEvent = window.loggedEvent || {};
window.loggedEvent.mouseup = event;
}, false);
});
page.sendEvent('click', 42, 217);
var event = page.evaluate(function() {
return window.loggedEvent;
});
assert_equals(event.mouseup.clientX, 42);
assert_equals(event.mouseup.clientY, 217);
assert_equals(event.mousedown.clientX, 42);
assert_equals(event.mousedown.clientY, 217);
// click with modifier key
page.evaluate(function() {
window.addEventListener('click', function(event) {
window.loggedEvent = window.loggedEvent || {};
window.loggedEvent.click = event;
}, false);
});
page.sendEvent('click', 100, 100, 'left', page.event.modifier.shift);
var event = page.evaluate(function() {
return window.loggedEvent.click;
});
assert_is_true(event.shiftKey);
}, "mouse click events");

View File

@ -0,0 +1,31 @@
//! unsupported
test(function () {
var page = require('webpage').create();
page.content = '<input id="doubleClickField" type="text" onclick="document.getElementById(\'doubleClickField\').value=\'clicked\';" ondblclick="document.getElementById(\'doubleClickField\').value=\'doubleclicked\';" oncontextmenu="document.getElementById(\'doubleClickField\').value=\'rightclicked\'; return false;" value="hello"/>';
var point = page.evaluate(function () {
var el = document.querySelector('input');
var rect = el.getBoundingClientRect();
return { x: rect.left + Math.floor(rect.width / 2), y: rect.top + (rect.height / 2) };
});
page.sendEvent('doubleclick', point.x, point.y);
var text = page.evaluate(function () {
return document.querySelector('input').value;
});
assert_equals(text, "doubleclicked");
// click with modifier key
page.evaluate(function() {
window.addEventListener('dblclick', function(event) {
window.loggedEvent = window.loggedEvent || {};
window.loggedEvent.dblclick = event;
}, false);
});
page.sendEvent('doubleclick', 100, 100, 'left', page.event.modifier.shift);
var event = page.evaluate(function() {
return window.loggedEvent.dblclick;
});
assert_is_true(event.shiftKey);
}, "mouse double-click events");

View File

@ -0,0 +1,27 @@
//! unsupported
test(function () {
var page = require('webpage').create();
page.evaluate(function() {
window.addEventListener('mousedown', function(event) {
window.loggedEvent = window.loggedEvent || [];
window.loggedEvent.push(event);
}, false);
});
page.sendEvent('mousedown', 42, 217);
var loggedEvent = page.evaluate(function() {
return window.loggedEvent;
});
assert_equals(loggedEvent.length, 1);
assert_equals(loggedEvent[0].clientX, 42);
assert_equals(loggedEvent[0].clientY, 217);
page.sendEvent('mousedown', 100, 100, 'left', page.event.modifier.shift);
loggedEvent = page.evaluate(function() {
return window.loggedEvent;
});
assert_equals(loggedEvent.length, 2);
assert_is_true(loggedEvent[1].shiftKey);
}, "mouse-down events");

View File

@ -0,0 +1,19 @@
//! unsupported
test(function () {
var page = require('webpage').create();
page.evaluate(function() {
window.addEventListener('mousemove', function(event) {
window.loggedEvent = window.loggedEvent || [];
window.loggedEvent.push(event);
}, false);
});
page.sendEvent('mousemove', 14, 3);
var loggedEvent = page.evaluate(function() {
return window.loggedEvent;
});
assert_equals(loggedEvent.length, 1);
assert_equals(loggedEvent[0].clientX, 14);
assert_equals(loggedEvent[0].clientY, 3);
}, "mouse-move events");

View File

@ -0,0 +1,27 @@
//! unsupported
test(function () {
var webpage = require('webpage');
var page = webpage.create();
page.evaluate(function() {
window.addEventListener('mouseup', function(event) {
window.loggedEvent = window.loggedEvent || [];
window.loggedEvent.push(event);
}, false);
});
page.sendEvent('mouseup', 42, 217);
var loggedEvent = page.evaluate(function() {
return window.loggedEvent;
});
assert_equals(loggedEvent.length, 1);
assert_equals(loggedEvent[0].clientX, 42);
assert_equals(loggedEvent[0].clientY, 217);
page.sendEvent('mouseup', 100, 100, 'left', page.event.modifier.shift);
loggedEvent = page.evaluate(function() {
return window.loggedEvent;
});
assert_equals(loggedEvent.length, 2);
assert_is_true(loggedEvent[1].shiftKey);
}, "mouse-up events");

View File

@ -0,0 +1,31 @@
//! unsupported
async_test(function () {
var page = require("webpage").create();
var url1 = TEST_HTTP_BASE + "navigation/index.html";
var url2 = TEST_HTTP_BASE + "navigation/dest.html";
var onLoadFinished1 = this.step_func(function (status) {
assert_equals(status, "success");
assert_equals(page.url, url1);
assert_equals(page.evaluate(function () {
return document.body.innerHTML;
}), "INDEX\n");
page.onLoadFinished = onLoadFinished2;
page.evaluate(function() {
window.location = "dest.html";
});
});
var onLoadFinished2 = this.step_func_done(function (status) {
assert_equals(status, "success");
assert_equals(page.url, url2);
assert_equals(page.evaluate(function () {
return document.body.innerHTML;
}), "DEST\n");
});
page.onLoadFinished = onLoadFinished1;
page.open(url1);
}, "navigating to a relative URL using window.location");

Some files were not shown because too many files have changed in this diff Show More