init commit

This commit is contained in:
iFlip721
2025-04-16 09:55:57 -04:00
commit a03209dc9e
935 changed files with 177492 additions and 0 deletions

View File

@@ -0,0 +1,27 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.wrapInASCIIBox = wrapInASCIIBox;
/**
* Copyright (c) Microsoft Corporation.
*
* 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.
*/
function wrapInASCIIBox(text, padding = 0) {
const lines = text.split('\n');
const maxLength = Math.max(...lines.map(line => line.length));
return ['╔' + '═'.repeat(maxLength + padding * 2) + '╗', ...lines.map(line => '║' + ' '.repeat(padding) + line + ' '.repeat(maxLength - line.length + padding) + '║'), '╚' + '═'.repeat(maxLength + padding * 2) + '╝'].join('\n');
}

View File

@@ -0,0 +1,171 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.getComparator = getComparator;
var _utilsBundle = require("../utilsBundle");
var _compare = require("../image_tools/compare");
/**
* Copyright 2017 Google Inc. All rights reserved.
* Modifications copyright (c) Microsoft Corporation.
*
* 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.
*/
const pixelmatch = require('../third_party/pixelmatch');
const {
diff_match_patch,
DIFF_INSERT,
DIFF_DELETE,
DIFF_EQUAL
} = require('../third_party/diff_match_patch');
function getComparator(mimeType) {
if (mimeType === 'image/png') return compareImages.bind(null, 'image/png');
if (mimeType === 'image/jpeg') return compareImages.bind(null, 'image/jpeg');
if (mimeType === 'text/plain') return compareText;
return compareBuffersOrStrings;
}
const JPEG_JS_MAX_BUFFER_SIZE_IN_MB = 5 * 1024; // ~5 GB
function compareBuffersOrStrings(actualBuffer, expectedBuffer) {
if (typeof actualBuffer === 'string') return compareText(actualBuffer, expectedBuffer);
if (!actualBuffer || !(actualBuffer instanceof Buffer)) return {
errorMessage: 'Actual result should be a Buffer or a string.'
};
if (Buffer.compare(actualBuffer, expectedBuffer)) return {
errorMessage: 'Buffers differ'
};
return null;
}
function compareImages(mimeType, actualBuffer, expectedBuffer, options = {}) {
var _options$_comparator, _ref;
if (!actualBuffer || !(actualBuffer instanceof Buffer)) return {
errorMessage: 'Actual result should be a Buffer.'
};
validateBuffer(expectedBuffer, mimeType);
let actual = mimeType === 'image/png' ? _utilsBundle.PNG.sync.read(actualBuffer) : _utilsBundle.jpegjs.decode(actualBuffer, {
maxMemoryUsageInMB: JPEG_JS_MAX_BUFFER_SIZE_IN_MB
});
let expected = mimeType === 'image/png' ? _utilsBundle.PNG.sync.read(expectedBuffer) : _utilsBundle.jpegjs.decode(expectedBuffer, {
maxMemoryUsageInMB: JPEG_JS_MAX_BUFFER_SIZE_IN_MB
});
const size = {
width: Math.max(expected.width, actual.width),
height: Math.max(expected.height, actual.height)
};
let sizesMismatchError = '';
if (expected.width !== actual.width || expected.height !== actual.height) {
sizesMismatchError = `Expected an image ${expected.width}px by ${expected.height}px, received ${actual.width}px by ${actual.height}px. `;
actual = resizeImage(actual, size);
expected = resizeImage(expected, size);
}
const diff = new _utilsBundle.PNG({
width: size.width,
height: size.height
});
let count;
if (options._comparator === 'ssim-cie94') {
count = (0, _compare.compare)(expected.data, actual.data, diff.data, size.width, size.height, {
// All ΔE* formulae are originally designed to have the difference of 1.0 stand for a "just noticeable difference" (JND).
// See https://en.wikipedia.org/wiki/Color_difference#CIELAB_%CE%94E*
maxColorDeltaE94: 1.0
});
} else if (((_options$_comparator = options._comparator) !== null && _options$_comparator !== void 0 ? _options$_comparator : 'pixelmatch') === 'pixelmatch') {
var _options$threshold;
count = pixelmatch(expected.data, actual.data, diff.data, size.width, size.height, {
threshold: (_options$threshold = options.threshold) !== null && _options$threshold !== void 0 ? _options$threshold : 0.2
});
} else {
throw new Error(`Configuration specifies unknown comparator "${options._comparator}"`);
}
const maxDiffPixels1 = options.maxDiffPixels;
const maxDiffPixels2 = options.maxDiffPixelRatio !== undefined ? expected.width * expected.height * options.maxDiffPixelRatio : undefined;
let maxDiffPixels;
if (maxDiffPixels1 !== undefined && maxDiffPixels2 !== undefined) maxDiffPixels = Math.min(maxDiffPixels1, maxDiffPixels2);else maxDiffPixels = (_ref = maxDiffPixels1 !== null && maxDiffPixels1 !== void 0 ? maxDiffPixels1 : maxDiffPixels2) !== null && _ref !== void 0 ? _ref : 0;
const ratio = Math.ceil(count / (expected.width * expected.height) * 100) / 100;
const pixelsMismatchError = count > maxDiffPixels ? `${count} pixels (ratio ${ratio.toFixed(2)} of all image pixels) are different.` : '';
if (pixelsMismatchError || sizesMismatchError) return {
errorMessage: sizesMismatchError + pixelsMismatchError,
diff: _utilsBundle.PNG.sync.write(diff)
};
return null;
}
function validateBuffer(buffer, mimeType) {
if (mimeType === 'image/png') {
const pngMagicNumber = [137, 80, 78, 71, 13, 10, 26, 10];
if (buffer.length < pngMagicNumber.length || !pngMagicNumber.every((byte, index) => buffer[index] === byte)) throw new Error('could not decode image as PNG.');
} else if (mimeType === 'image/jpeg') {
const jpegMagicNumber = [255, 216];
if (buffer.length < jpegMagicNumber.length || !jpegMagicNumber.every((byte, index) => buffer[index] === byte)) throw new Error('could not decode image as JPEG.');
}
}
function compareText(actual, expectedBuffer) {
if (typeof actual !== 'string') return {
errorMessage: 'Actual result should be a string'
};
const expected = expectedBuffer.toString('utf-8');
if (expected === actual) return null;
const dmp = new diff_match_patch();
const d = dmp.diff_main(expected, actual);
dmp.diff_cleanupSemantic(d);
return {
errorMessage: diff_prettyTerminal(d)
};
}
function diff_prettyTerminal(diffs) {
const html = [];
for (let x = 0; x < diffs.length; x++) {
const op = diffs[x][0]; // Operation (insert, delete, equal)
const data = diffs[x][1]; // Text of change.
const text = data;
switch (op) {
case DIFF_INSERT:
html[x] = _utilsBundle.colors.green(text);
break;
case DIFF_DELETE:
html[x] = _utilsBundle.colors.reset(_utilsBundle.colors.strikethrough(_utilsBundle.colors.red(text)));
break;
case DIFF_EQUAL:
html[x] = text;
break;
}
}
return html.join('');
}
function resizeImage(image, size) {
if (image.width === size.width && image.height === size.height) return image;
const buffer = new Uint8Array(size.width * size.height * 4);
for (let y = 0; y < size.height; y++) {
for (let x = 0; x < size.width; x++) {
const to = (y * size.width + x) * 4;
if (y < image.height && x < image.width) {
const from = (y * image.width + x) * 4;
buffer[to] = image.data[from];
buffer[to + 1] = image.data[from + 1];
buffer[to + 2] = image.data[from + 2];
buffer[to + 3] = image.data[from + 3];
} else {
buffer[to] = 0;
buffer[to + 1] = 0;
buffer[to + 2] = 0;
buffer[to + 3] = 0;
}
}
}
return {
data: Buffer.from(buffer),
width: size.width,
height: size.height
};
}

View File

@@ -0,0 +1,33 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.calculateSha1 = calculateSha1;
exports.createGuid = createGuid;
var _crypto = _interopRequireDefault(require("crypto"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* Copyright (c) Microsoft Corporation.
*
* 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.
*/
function createGuid() {
return _crypto.default.randomBytes(16).toString('hex');
}
function calculateSha1(buffer) {
const hash = _crypto.default.createHash('sha1');
hash.update(buffer);
return hash.digest('hex');
}

View File

@@ -0,0 +1,46 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.assert = assert;
exports.debugAssert = debugAssert;
exports.debugMode = debugMode;
exports.isUnderTest = isUnderTest;
exports.setUnderTest = setUnderTest;
var _env = require("./env");
/**
* Copyright (c) Microsoft Corporation.
*
* 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.
*/
function assert(value, message) {
if (!value) throw new Error(message || 'Assertion error');
}
function debugAssert(value, message) {
if (isUnderTest() && !value) throw new Error(message);
}
const debugEnv = (0, _env.getFromENV)('PWDEBUG') || '';
function debugMode() {
if (debugEnv === 'console') return 'console';
if (debugEnv === '0' || debugEnv === 'false') return '';
return debugEnv ? 'inspector' : '';
}
let _isUnderTest = !!process.env.PWTEST_UNDER_TEST;
function setUnderTest() {
_isUnderTest = true;
}
function isUnderTest() {
return _isUnderTest;
}

View File

@@ -0,0 +1,40 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.getAsBooleanFromENV = getAsBooleanFromENV;
exports.getFromENV = getFromENV;
exports.getPackageManager = getPackageManager;
/**
* Copyright (c) Microsoft Corporation.
*
* 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.
*/
function getFromENV(name) {
let value = process.env[name];
value = value === undefined ? process.env[`npm_config_${name.toLowerCase()}`] : value;
value = value === undefined ? process.env[`npm_package_config_${name.toLowerCase()}`] : value;
return value;
}
function getAsBooleanFromENV(name) {
const value = getFromENV(name);
return !!value && value !== 'false' && value !== '0';
}
function getPackageManager() {
const env = process.env.npm_config_user_agent || '';
if (env.includes('yarn')) return 'yarn';
if (env.includes('pnpm')) return 'pnpm';
return 'npm';
}

View File

@@ -0,0 +1,39 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.eventsHelper = void 0;
/**
* Copyright 2017 Google Inc. All rights reserved.
* Modifications copyright (c) Microsoft Corporation.
*
* 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.
*/
class EventsHelper {
static addEventListener(emitter, eventName, handler) {
emitter.on(eventName, handler);
return {
emitter,
eventName,
handler
};
}
static removeEventListeners(listeners) {
for (const listener of listeners) listener.emitter.removeListener(listener.eventName, listener.handler);
listeners.splice(0, listeners.length);
}
}
const eventsHelper = EventsHelper;
exports.eventsHelper = eventsHelper;

View File

@@ -0,0 +1,62 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.canAccessFile = canAccessFile;
exports.copyFileAndMakeWritable = copyFileAndMakeWritable;
exports.existsAsync = void 0;
exports.mkdirIfNeeded = mkdirIfNeeded;
exports.removeFolders = removeFolders;
var _fs = _interopRequireDefault(require("fs"));
var _path = _interopRequireDefault(require("path"));
var _utilsBundle = require("../utilsBundle");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* Copyright (c) Microsoft Corporation.
*
* 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.
*/
const existsAsync = path => new Promise(resolve => _fs.default.stat(path, err => resolve(!err)));
exports.existsAsync = existsAsync;
async function mkdirIfNeeded(filePath) {
// This will harmlessly throw on windows if the dirname is the root directory.
await _fs.default.promises.mkdir(_path.default.dirname(filePath), {
recursive: true
}).catch(() => {});
}
async function removeFolders(dirs) {
return await Promise.all(dirs.map(dir => {
return new Promise(fulfill => {
(0, _utilsBundle.rimraf)(dir, {
maxBusyTries: 10
}, error => {
fulfill(error !== null && error !== void 0 ? error : undefined);
});
});
}));
}
function canAccessFile(file) {
if (!file) return false;
try {
_fs.default.accessSync(file);
return true;
} catch (e) {
return false;
}
}
async function copyFileAndMakeWritable(from, to) {
await _fs.default.promises.copyFile(from, to);
await _fs.default.promises.chmod(to, 0o664);
}

View File

@@ -0,0 +1,75 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.globToRegex = globToRegex;
/**
* Copyright (c) Microsoft Corporation.
*
* 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.
*/
const escapeGlobChars = new Set(['/', '$', '^', '+', '.', '(', ')', '=', '!', '|']);
function globToRegex(glob) {
const tokens = ['^'];
let inGroup;
for (let i = 0; i < glob.length; ++i) {
const c = glob[i];
if (escapeGlobChars.has(c)) {
tokens.push('\\' + c);
continue;
}
if (c === '*') {
const beforeDeep = glob[i - 1];
let starCount = 1;
while (glob[i + 1] === '*') {
starCount++;
i++;
}
const afterDeep = glob[i + 1];
const isDeep = starCount > 1 && (beforeDeep === '/' || beforeDeep === undefined) && (afterDeep === '/' || afterDeep === undefined);
if (isDeep) {
tokens.push('((?:[^/]*(?:\/|$))*)');
i++;
} else {
tokens.push('([^/]*)');
}
continue;
}
switch (c) {
case '?':
tokens.push('.');
break;
case '{':
inGroup = true;
tokens.push('(');
break;
case '}':
inGroup = false;
tokens.push(')');
break;
case ',':
if (inGroup) {
tokens.push('|');
break;
}
tokens.push('\\' + c);
break;
default:
tokens.push(c);
}
}
tokens.push('$');
return new RegExp(tokens.join(''));
}

View File

@@ -0,0 +1,155 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.createSocket = createSocket;
exports.httpsHappyEyeballsAgent = exports.httpHappyEyeballsAgent = void 0;
var dns = _interopRequireWildcard(require("dns"));
var http = _interopRequireWildcard(require("http"));
var https = _interopRequireWildcard(require("https"));
var net = _interopRequireWildcard(require("net"));
var tls = _interopRequireWildcard(require("tls"));
var _manualPromise = require("./manualPromise");
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
/**
* Copyright (c) Microsoft Corporation.
*
* 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.
*/
// Implementation(partial) of Happy Eyeballs 2 algorithm described in
// https://www.rfc-editor.org/rfc/rfc8305
// Same as in Chromium (https://source.chromium.org/chromium/chromium/src/+/5666ff4f5077a7e2f72902f3a95f5d553ea0d88d:net/socket/transport_connect_job.cc;l=102)
const connectionAttemptDelayMs = 300;
class HttpHappyEyeballsAgent extends http.Agent {
createConnection(options, oncreate) {
// There is no ambiguity in case of IP address.
if (net.isIP(clientRequestArgsToHostName(options))) return net.createConnection(options);
createConnectionAsync(options, oncreate, /* useTLS */false).catch(err => oncreate === null || oncreate === void 0 ? void 0 : oncreate(err));
}
}
class HttpsHappyEyeballsAgent extends https.Agent {
createConnection(options, oncreate) {
// There is no ambiguity in case of IP address.
if (net.isIP(clientRequestArgsToHostName(options))) return tls.connect(options);
createConnectionAsync(options, oncreate, /* useTLS */true).catch(err => oncreate === null || oncreate === void 0 ? void 0 : oncreate(err));
}
}
const httpsHappyEyeballsAgent = new HttpsHappyEyeballsAgent();
exports.httpsHappyEyeballsAgent = httpsHappyEyeballsAgent;
const httpHappyEyeballsAgent = new HttpHappyEyeballsAgent();
exports.httpHappyEyeballsAgent = httpHappyEyeballsAgent;
async function createSocket(host, port) {
return new Promise((resolve, reject) => {
if (net.isIP(host)) {
const socket = net.createConnection({
host,
port
});
socket.on('connect', () => resolve(socket));
socket.on('error', error => reject(error));
} else {
createConnectionAsync({
host,
port
}, (err, socket) => {
if (err) reject(err);
if (socket) resolve(socket);
}, /* useTLS */false).catch(err => reject(err));
}
});
}
async function createConnectionAsync(options, oncreate, useTLS) {
const lookup = options.__testHookLookup || lookupAddresses;
const hostname = clientRequestArgsToHostName(options);
const addresses = await lookup(hostname);
const sockets = new Set();
let firstError;
let errorCount = 0;
const handleError = (socket, err) => {
var _firstError;
if (!sockets.delete(socket)) return;
++errorCount;
(_firstError = firstError) !== null && _firstError !== void 0 ? _firstError : firstError = err;
if (errorCount === addresses.length) oncreate === null || oncreate === void 0 ? void 0 : oncreate(firstError);
};
const connected = new _manualPromise.ManualPromise();
for (const {
address
} of addresses) {
const socket = useTLS ? tls.connect({
...options,
port: options.port,
host: address,
servername: hostname
}) : net.createConnection({
...options,
port: options.port,
host: address
});
// Each socket may fire only one of 'connect', 'timeout' or 'error' events.
// None of these events are fired after socket.destroy() is called.
socket.on('connect', () => {
connected.resolve();
oncreate === null || oncreate === void 0 ? void 0 : oncreate(null, socket);
// TODO: Cache the result?
// Close other outstanding sockets.
sockets.delete(socket);
for (const s of sockets) s.destroy();
sockets.clear();
});
socket.on('timeout', () => {
// Timeout is not an error, so we have to manually close the socket.
socket.destroy();
handleError(socket, new Error('Connection timeout'));
});
socket.on('error', e => handleError(socket, e));
sockets.add(socket);
await Promise.race([connected, new Promise(f => setTimeout(f, connectionAttemptDelayMs))]);
if (connected.isDone()) break;
}
}
async function lookupAddresses(hostname) {
const addresses = await dns.promises.lookup(hostname, {
all: true,
family: 0,
verbatim: true
});
let firstFamily = addresses.filter(({
family
}) => family === 6);
let secondFamily = addresses.filter(({
family
}) => family === 4);
// Make sure first address in the list is the same as in the original order.
if (firstFamily.length && firstFamily[0] !== addresses[0]) {
const tmp = firstFamily;
firstFamily = secondFamily;
secondFamily = tmp;
}
const result = [];
// Alternate ipv6 and ipv4 addreses.
for (let i = 0; i < Math.max(firstFamily.length, secondFamily.length); i++) {
if (firstFamily[i]) result.push(firstFamily[i]);
if (secondFamily[i]) result.push(secondFamily[i]);
}
return result;
}
function clientRequestArgsToHostName(options) {
if (options.hostname) return options.hostname;
if (options.host) return options.host;
throw new Error('Either options.hostname or options.host must be provided');
}

View File

@@ -0,0 +1,52 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.headersArrayToObject = headersArrayToObject;
exports.headersObjectToArray = headersObjectToArray;
/**
* Copyright (c) Microsoft Corporation.
*
* 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.
*/
function headersObjectToArray(headers, separator, setCookieSeparator) {
if (!setCookieSeparator) setCookieSeparator = separator;
const result = [];
for (const name in headers) {
const values = headers[name];
if (values === undefined) continue;
if (separator) {
const sep = name.toLowerCase() === 'set-cookie' ? setCookieSeparator : separator;
for (const value of values.split(sep)) result.push({
name,
value: value.trim()
});
} else {
result.push({
name,
value: values
});
}
}
return result;
}
function headersArrayToObject(headers, lowerCase) {
const result = {};
for (const {
name,
value
} of headers) result[lowerCase ? name.toLowerCase() : name] = value;
return result;
}

View File

@@ -0,0 +1,65 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.hostPlatform = void 0;
var _os = _interopRequireDefault(require("os"));
var _linuxUtils = require("./linuxUtils");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* Copyright (c) Microsoft Corporation.
*
* 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.
*/
const hostPlatform = (() => {
const platform = _os.default.platform();
if (platform === 'darwin') {
const ver = _os.default.release().split('.').map(a => parseInt(a, 10));
let macVersion = '';
if (ver[0] < 18) {
// Everything before 10.14 is considered 10.13.
macVersion = 'mac10.13';
} else if (ver[0] === 18) {
macVersion = 'mac10.14';
} else if (ver[0] === 19) {
macVersion = 'mac10.15';
} else {
// ver[0] >= 20
const LAST_STABLE_MAC_MAJOR_VERSION = 13;
// Best-effort support for MacOS beta versions.
macVersion = 'mac' + Math.min(ver[0] - 9, LAST_STABLE_MAC_MAJOR_VERSION);
// BigSur is the first version that might run on Apple Silicon.
if (_os.default.cpus().some(cpu => cpu.model.includes('Apple'))) macVersion += '-arm64';
}
return macVersion;
}
if (platform === 'linux') {
const archSuffix = _os.default.arch() === 'arm64' ? '-arm64' : '';
const distroInfo = (0, _linuxUtils.getLinuxDistributionInfoSync)();
// Pop!_OS is ubuntu-based and has the same versions.
// KDE Neon is ubuntu-based and has the same versions.
if ((distroInfo === null || distroInfo === void 0 ? void 0 : distroInfo.id) === 'ubuntu' || (distroInfo === null || distroInfo === void 0 ? void 0 : distroInfo.id) === 'pop' || (distroInfo === null || distroInfo === void 0 ? void 0 : distroInfo.id) === 'neon') {
if (parseInt(distroInfo.version, 10) <= 19) return 'ubuntu18.04' + archSuffix;
if (parseInt(distroInfo.version, 10) <= 21) return 'ubuntu20.04' + archSuffix;
return 'ubuntu22.04' + archSuffix;
}
if ((distroInfo === null || distroInfo === void 0 ? void 0 : distroInfo.id) === 'debian' && (distroInfo === null || distroInfo === void 0 ? void 0 : distroInfo.version) === '11') return 'debian11' + archSuffix;
return 'generic-linux' + archSuffix;
}
if (platform === 'win32') return 'win64';
return '<unknown>';
})();
exports.hostPlatform = hostPlatform;

View File

@@ -0,0 +1,195 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.HttpServer = void 0;
var _fs = _interopRequireDefault(require("fs"));
var _path = _interopRequireDefault(require("path"));
var _utilsBundle = require("../utilsBundle");
var _debug = require("./debug");
var _network = require("./network");
var _manualPromise = require("./manualPromise");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* Copyright (c) Microsoft Corporation.
*
* 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.
*/
class HttpServer {
constructor(address = '') {
this._server = void 0;
this._urlPrefix = void 0;
this._port = 0;
this._started = false;
this._routes = [];
this._urlPrefix = address;
this._server = (0, _network.createHttpServer)(this._onRequest.bind(this));
}
server() {
return this._server;
}
routePrefix(prefix, handler) {
this._routes.push({
prefix,
handler
});
}
routePath(path, handler) {
this._routes.push({
exact: path,
handler
});
}
port() {
return this._port;
}
async _tryStart(port, host) {
const errorPromise = new _manualPromise.ManualPromise();
const errorListener = error => errorPromise.reject(error);
this._server.on('error', errorListener);
try {
this._server.listen(port, host);
await Promise.race([new Promise(cb => this._server.once('listening', cb)), errorPromise]);
} finally {
this._server.removeListener('error', errorListener);
}
}
async start(options = {}) {
(0, _debug.assert)(!this._started, 'server already started');
this._started = true;
const host = options.host || 'localhost';
if (options.preferredPort) {
try {
await this._tryStart(options.preferredPort, host);
} catch (e) {
if (!e || !e.message || !e.message.includes('EADDRINUSE')) throw e;
await this._tryStart(undefined, host);
}
} else {
await this._tryStart(options.port, host);
}
const address = this._server.address();
(0, _debug.assert)(address, 'Could not bind server socket');
if (!this._urlPrefix) {
if (typeof address === 'string') {
this._urlPrefix = address;
} else {
this._port = address.port;
this._urlPrefix = `http://${host}:${address.port}`;
}
}
return this._urlPrefix;
}
async stop() {
await new Promise(cb => this._server.close(cb));
}
urlPrefix() {
return this._urlPrefix;
}
serveFile(request, response, absoluteFilePath, headers) {
try {
for (const [name, value] of Object.entries(headers || {})) response.setHeader(name, value);
if (request.headers.range) this._serveRangeFile(request, response, absoluteFilePath);else this._serveFile(response, absoluteFilePath);
return true;
} catch (e) {
return false;
}
}
_serveFile(response, absoluteFilePath) {
const content = _fs.default.readFileSync(absoluteFilePath);
response.statusCode = 200;
const contentType = _utilsBundle.mime.getType(_path.default.extname(absoluteFilePath)) || 'application/octet-stream';
response.setHeader('Content-Type', contentType);
response.setHeader('Content-Length', content.byteLength);
response.end(content);
}
_serveRangeFile(request, response, absoluteFilePath) {
const range = request.headers.range;
if (!range || !range.startsWith('bytes=') || range.includes(', ') || [...range].filter(char => char === '-').length !== 1) {
response.statusCode = 400;
return response.end('Bad request');
}
// Parse the range header: https://datatracker.ietf.org/doc/html/rfc7233#section-2.1
const [startStr, endStr] = range.replace(/bytes=/, '').split('-');
// Both start and end (when passing to fs.createReadStream) and the range header are inclusive and start counting at 0.
let start;
let end;
const size = _fs.default.statSync(absoluteFilePath).size;
if (startStr !== '' && endStr === '') {
// No end specified: use the whole file
start = +startStr;
end = size - 1;
} else if (startStr === '' && endStr !== '') {
// No start specified: calculate start manually
start = size - +endStr;
end = size - 1;
} else {
start = +startStr;
end = +endStr;
}
// Handle unavailable range request
if (Number.isNaN(start) || Number.isNaN(end) || start >= size || end >= size || start > end) {
// Return the 416 Range Not Satisfiable: https://datatracker.ietf.org/doc/html/rfc7233#section-4.4
response.writeHead(416, {
'Content-Range': `bytes */${size}`
});
return response.end();
}
// Sending Partial Content: https://datatracker.ietf.org/doc/html/rfc7233#section-4.1
response.writeHead(206, {
'Content-Range': `bytes ${start}-${end}/${size}`,
'Accept-Ranges': 'bytes',
'Content-Length': end - start + 1,
'Content-Type': _utilsBundle.mime.getType(_path.default.extname(absoluteFilePath))
});
const readable = _fs.default.createReadStream(absoluteFilePath, {
start,
end
});
readable.pipe(response);
}
_onRequest(request, response) {
response.setHeader('Access-Control-Allow-Origin', '*');
response.setHeader('Access-Control-Request-Method', '*');
response.setHeader('Access-Control-Allow-Methods', 'OPTIONS, GET');
if (request.headers.origin) response.setHeader('Access-Control-Allow-Headers', request.headers.origin);
if (request.method === 'OPTIONS') {
response.writeHead(200);
response.end();
return;
}
request.on('error', () => response.end());
try {
if (!request.url) {
response.end();
return;
}
const url = new URL('http://localhost' + request.url);
for (const route of this._routes) {
if (route.exact && url.pathname === route.exact && route.handler(request, response)) return;
if (route.prefix && url.pathname.startsWith(route.prefix) && route.handler(request, response)) return;
}
response.statusCode = 404;
response.end();
} catch (e) {
response.end();
}
}
}
exports.HttpServer = HttpServer;

View File

@@ -0,0 +1,302 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
var _ascii = require("./ascii");
Object.keys(_ascii).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (key in exports && exports[key] === _ascii[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _ascii[key];
}
});
});
var _comparators = require("./comparators");
Object.keys(_comparators).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (key in exports && exports[key] === _comparators[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _comparators[key];
}
});
});
var _crypto = require("./crypto");
Object.keys(_crypto).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (key in exports && exports[key] === _crypto[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _crypto[key];
}
});
});
var _debug = require("./debug");
Object.keys(_debug).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (key in exports && exports[key] === _debug[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _debug[key];
}
});
});
var _env = require("./env");
Object.keys(_env).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (key in exports && exports[key] === _env[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _env[key];
}
});
});
var _eventsHelper = require("./eventsHelper");
Object.keys(_eventsHelper).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (key in exports && exports[key] === _eventsHelper[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _eventsHelper[key];
}
});
});
var _fileUtils = require("./fileUtils");
Object.keys(_fileUtils).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (key in exports && exports[key] === _fileUtils[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _fileUtils[key];
}
});
});
var _glob = require("./glob");
Object.keys(_glob).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (key in exports && exports[key] === _glob[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _glob[key];
}
});
});
var _headers = require("./headers");
Object.keys(_headers).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (key in exports && exports[key] === _headers[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _headers[key];
}
});
});
var _httpServer = require("./httpServer");
Object.keys(_httpServer).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (key in exports && exports[key] === _httpServer[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _httpServer[key];
}
});
});
var _manualPromise = require("./manualPromise");
Object.keys(_manualPromise).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (key in exports && exports[key] === _manualPromise[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _manualPromise[key];
}
});
});
var _mimeType = require("./mimeType");
Object.keys(_mimeType).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (key in exports && exports[key] === _mimeType[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _mimeType[key];
}
});
});
var _multimap = require("./multimap");
Object.keys(_multimap).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (key in exports && exports[key] === _multimap[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _multimap[key];
}
});
});
var _network = require("./network");
Object.keys(_network).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (key in exports && exports[key] === _network[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _network[key];
}
});
});
var _processLauncher = require("./processLauncher");
Object.keys(_processLauncher).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (key in exports && exports[key] === _processLauncher[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _processLauncher[key];
}
});
});
var _profiler = require("./profiler");
Object.keys(_profiler).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (key in exports && exports[key] === _profiler[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _profiler[key];
}
});
});
var _rtti = require("./rtti");
Object.keys(_rtti).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (key in exports && exports[key] === _rtti[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _rtti[key];
}
});
});
var _spawnAsync = require("./spawnAsync");
Object.keys(_spawnAsync).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (key in exports && exports[key] === _spawnAsync[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _spawnAsync[key];
}
});
});
var _stackTrace = require("./stackTrace");
Object.keys(_stackTrace).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (key in exports && exports[key] === _stackTrace[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _stackTrace[key];
}
});
});
var _task = require("./task");
Object.keys(_task).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (key in exports && exports[key] === _task[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _task[key];
}
});
});
var _time = require("./time");
Object.keys(_time).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (key in exports && exports[key] === _time[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _time[key];
}
});
});
var _timeoutRunner = require("./timeoutRunner");
Object.keys(_timeoutRunner).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (key in exports && exports[key] === _timeoutRunner[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _timeoutRunner[key];
}
});
});
var _traceUtils = require("./traceUtils");
Object.keys(_traceUtils).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (key in exports && exports[key] === _traceUtils[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _traceUtils[key];
}
});
});
var _userAgent = require("./userAgent");
Object.keys(_userAgent).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (key in exports && exports[key] === _userAgent[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _userAgent[key];
}
});
});
var _zipFile = require("./zipFile");
Object.keys(_zipFile).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (key in exports && exports[key] === _zipFile[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _zipFile[key];
}
});
});
var _zones = require("./zones");
Object.keys(_zones).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (key in exports && exports[key] === _zones[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _zones[key];
}
});
});
var _locatorGenerators = require("./isomorphic/locatorGenerators");
Object.keys(_locatorGenerators).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (key in exports && exports[key] === _locatorGenerators[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _locatorGenerators[key];
}
});
});

View File

@@ -0,0 +1,238 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.InvalidSelectorError = void 0;
exports.isInvalidSelectorError = isInvalidSelectorError;
exports.parseCSS = parseCSS;
exports.serializeSelector = serializeSelector;
var css = _interopRequireWildcard(require("./cssTokenizer"));
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
/**
* Copyright (c) Microsoft Corporation.
*
* 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.
*/
class InvalidSelectorError extends Error {}
exports.InvalidSelectorError = InvalidSelectorError;
function isInvalidSelectorError(error) {
return error instanceof InvalidSelectorError;
}
// Note: '>=' is used internally for text engine to preserve backwards compatibility.
// TODO: consider
// - key=value
// - operators like `=`, `|=`, `~=`, `*=`, `/`
// - <empty>~=value
// - argument modes: "parse all", "parse commas", "just a string"
function parseCSS(selector, customNames) {
let tokens;
try {
tokens = css.tokenize(selector);
if (!(tokens[tokens.length - 1] instanceof css.EOFToken)) tokens.push(new css.EOFToken());
} catch (e) {
const newMessage = e.message + ` while parsing selector "${selector}"`;
const index = (e.stack || '').indexOf(e.message);
if (index !== -1) e.stack = e.stack.substring(0, index) + newMessage + e.stack.substring(index + e.message.length);
e.message = newMessage;
throw e;
}
const unsupportedToken = tokens.find(token => {
return token instanceof css.AtKeywordToken || token instanceof css.BadStringToken || token instanceof css.BadURLToken || token instanceof css.ColumnToken || token instanceof css.CDOToken || token instanceof css.CDCToken || token instanceof css.SemicolonToken ||
// TODO: Consider using these for something, e.g. to escape complex strings.
// For example :xpath{ (//div/bar[@attr="foo"])[2]/baz }
// Or this way :xpath( {complex-xpath-goes-here("hello")} )
token instanceof css.OpenCurlyToken || token instanceof css.CloseCurlyToken ||
// TODO: Consider treating these as strings?
token instanceof css.URLToken || token instanceof css.PercentageToken;
});
if (unsupportedToken) throw new InvalidSelectorError(`Unsupported token "${unsupportedToken.toSource()}" while parsing selector "${selector}"`);
let pos = 0;
const names = new Set();
function unexpected() {
return new InvalidSelectorError(`Unexpected token "${tokens[pos].toSource()}" while parsing selector "${selector}"`);
}
function skipWhitespace() {
while (tokens[pos] instanceof css.WhitespaceToken) pos++;
}
function isIdent(p = pos) {
return tokens[p] instanceof css.IdentToken;
}
function isString(p = pos) {
return tokens[p] instanceof css.StringToken;
}
function isNumber(p = pos) {
return tokens[p] instanceof css.NumberToken;
}
function isComma(p = pos) {
return tokens[p] instanceof css.CommaToken;
}
function isCloseParen(p = pos) {
return tokens[p] instanceof css.CloseParenToken;
}
function isStar(p = pos) {
return tokens[p] instanceof css.DelimToken && tokens[p].value === '*';
}
function isEOF(p = pos) {
return tokens[p] instanceof css.EOFToken;
}
function isClauseCombinator(p = pos) {
return tokens[p] instanceof css.DelimToken && ['>', '+', '~'].includes(tokens[p].value);
}
function isSelectorClauseEnd(p = pos) {
return isComma(p) || isCloseParen(p) || isEOF(p) || isClauseCombinator(p) || tokens[p] instanceof css.WhitespaceToken;
}
function consumeFunctionArguments() {
const result = [consumeArgument()];
while (true) {
skipWhitespace();
if (!isComma()) break;
pos++;
result.push(consumeArgument());
}
return result;
}
function consumeArgument() {
skipWhitespace();
if (isNumber()) return tokens[pos++].value;
if (isString()) return tokens[pos++].value;
return consumeComplexSelector();
}
function consumeComplexSelector() {
const result = {
simples: []
};
skipWhitespace();
if (isClauseCombinator()) {
// Put implicit ":scope" at the start. https://drafts.csswg.org/selectors-4/#absolutize
result.simples.push({
selector: {
functions: [{
name: 'scope',
args: []
}]
},
combinator: ''
});
} else {
result.simples.push({
selector: consumeSimpleSelector(),
combinator: ''
});
}
while (true) {
skipWhitespace();
if (isClauseCombinator()) {
result.simples[result.simples.length - 1].combinator = tokens[pos++].value;
skipWhitespace();
} else if (isSelectorClauseEnd()) {
break;
}
result.simples.push({
combinator: '',
selector: consumeSimpleSelector()
});
}
return result;
}
function consumeSimpleSelector() {
let rawCSSString = '';
const functions = [];
while (!isSelectorClauseEnd()) {
if (isIdent() || isStar()) {
rawCSSString += tokens[pos++].toSource();
} else if (tokens[pos] instanceof css.HashToken) {
rawCSSString += tokens[pos++].toSource();
} else if (tokens[pos] instanceof css.DelimToken && tokens[pos].value === '.') {
pos++;
if (isIdent()) rawCSSString += '.' + tokens[pos++].toSource();else throw unexpected();
} else if (tokens[pos] instanceof css.ColonToken) {
pos++;
if (isIdent()) {
if (!customNames.has(tokens[pos].value.toLowerCase())) {
rawCSSString += ':' + tokens[pos++].toSource();
} else {
const name = tokens[pos++].value.toLowerCase();
functions.push({
name,
args: []
});
names.add(name);
}
} else if (tokens[pos] instanceof css.FunctionToken) {
const name = tokens[pos++].value.toLowerCase();
if (!customNames.has(name)) {
rawCSSString += `:${name}(${consumeBuiltinFunctionArguments()})`;
} else {
functions.push({
name,
args: consumeFunctionArguments()
});
names.add(name);
}
skipWhitespace();
if (!isCloseParen()) throw unexpected();
pos++;
} else {
throw unexpected();
}
} else if (tokens[pos] instanceof css.OpenSquareToken) {
rawCSSString += '[';
pos++;
while (!(tokens[pos] instanceof css.CloseSquareToken) && !isEOF()) rawCSSString += tokens[pos++].toSource();
if (!(tokens[pos] instanceof css.CloseSquareToken)) throw unexpected();
rawCSSString += ']';
pos++;
} else {
throw unexpected();
}
}
if (!rawCSSString && !functions.length) throw unexpected();
return {
css: rawCSSString || undefined,
functions
};
}
function consumeBuiltinFunctionArguments() {
let s = '';
while (!isCloseParen() && !isEOF()) s += tokens[pos++].toSource();
return s;
}
const result = consumeFunctionArguments();
if (!isEOF()) throw new InvalidSelectorError(`Error while parsing selector "${selector}"`);
if (result.some(arg => typeof arg !== 'object' || !('simples' in arg))) throw new InvalidSelectorError(`Error while parsing selector "${selector}"`);
return {
selector: result,
names: Array.from(names)
};
}
function serializeSelector(args) {
return args.map(arg => {
if (typeof arg === 'string') return `"${arg}"`;
if (typeof arg === 'number') return String(arg);
return arg.simples.map(({
selector,
combinator
}) => {
let s = selector.css || '';
s = s + selector.functions.map(func => `:${func.name}(${serializeSelector(func.args)})`).join('');
if (combinator) s += ' ' + combinator;
return s;
}).join(' ');
}).join(', ');
}

View File

@@ -0,0 +1,979 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.WhitespaceToken = exports.URLToken = exports.SuffixMatchToken = exports.SubstringMatchToken = exports.StringValuedToken = exports.StringToken = exports.SemicolonToken = exports.PrefixMatchToken = exports.PercentageToken = exports.OpenSquareToken = exports.OpenParenToken = exports.OpenCurlyToken = exports.NumberToken = exports.InvalidCharacterError = exports.IncludeMatchToken = exports.IdentToken = exports.HashToken = exports.GroupingToken = exports.FunctionToken = exports.EOFToken = exports.DimensionToken = exports.DelimToken = exports.DashMatchToken = exports.CommaToken = exports.ColumnToken = exports.ColonToken = exports.CloseSquareToken = exports.CloseParenToken = exports.CloseCurlyToken = exports.CSSParserToken = exports.CDOToken = exports.CDCToken = exports.BadURLToken = exports.BadStringToken = exports.AtKeywordToken = void 0;
exports.tokenize = tokenize;
/* eslint-disable notice/notice */
/*
* The code in this file is licensed under the CC0 license.
* http://creativecommons.org/publicdomain/zero/1.0/
* It is free to use for any purpose. No attribution, permission, or reproduction of this license is required.
*/
// Original at https://github.com/tabatkins/parse-css
// Changes:
// - JS is replaced with TS.
// - Universal Module Definition wrapper is removed.
// - Everything not related to tokenizing - below the first exports block - is removed.
const between = function (num, first, last) {
return num >= first && num <= last;
};
function digit(code) {
return between(code, 0x30, 0x39);
}
function hexdigit(code) {
return digit(code) || between(code, 0x41, 0x46) || between(code, 0x61, 0x66);
}
function uppercaseletter(code) {
return between(code, 0x41, 0x5a);
}
function lowercaseletter(code) {
return between(code, 0x61, 0x7a);
}
function letter(code) {
return uppercaseletter(code) || lowercaseletter(code);
}
function nonascii(code) {
return code >= 0x80;
}
function namestartchar(code) {
return letter(code) || nonascii(code) || code === 0x5f;
}
function namechar(code) {
return namestartchar(code) || digit(code) || code === 0x2d;
}
function nonprintable(code) {
return between(code, 0, 8) || code === 0xb || between(code, 0xe, 0x1f) || code === 0x7f;
}
function newline(code) {
return code === 0xa;
}
function whitespace(code) {
return newline(code) || code === 9 || code === 0x20;
}
const maximumallowedcodepoint = 0x10ffff;
class InvalidCharacterError extends Error {
constructor(message) {
super(message);
this.name = 'InvalidCharacterError';
}
}
exports.InvalidCharacterError = InvalidCharacterError;
function preprocess(str) {
// Turn a string into an array of code points,
// following the preprocessing cleanup rules.
const codepoints = [];
for (let i = 0; i < str.length; i++) {
let code = str.charCodeAt(i);
if (code === 0xd && str.charCodeAt(i + 1) === 0xa) {
code = 0xa;
i++;
}
if (code === 0xd || code === 0xc) code = 0xa;
if (code === 0x0) code = 0xfffd;
if (between(code, 0xd800, 0xdbff) && between(str.charCodeAt(i + 1), 0xdc00, 0xdfff)) {
// Decode a surrogate pair into an astral codepoint.
const lead = code - 0xd800;
const trail = str.charCodeAt(i + 1) - 0xdc00;
code = Math.pow(2, 16) + lead * Math.pow(2, 10) + trail;
i++;
}
codepoints.push(code);
}
return codepoints;
}
function stringFromCode(code) {
if (code <= 0xffff) return String.fromCharCode(code);
// Otherwise, encode astral char as surrogate pair.
code -= Math.pow(2, 16);
const lead = Math.floor(code / Math.pow(2, 10)) + 0xd800;
const trail = code % Math.pow(2, 10) + 0xdc00;
return String.fromCharCode(lead) + String.fromCharCode(trail);
}
function tokenize(str1) {
const str = preprocess(str1);
let i = -1;
const tokens = [];
let code;
// Line number information.
let line = 0;
let column = 0;
// The only use of lastLineLength is in reconsume().
let lastLineLength = 0;
const incrLineno = function () {
line += 1;
lastLineLength = column;
column = 0;
};
const locStart = {
line: line,
column: column
};
const codepoint = function (i) {
if (i >= str.length) return -1;
return str[i];
};
const next = function (num) {
if (num === undefined) num = 1;
if (num > 3) throw 'Spec Error: no more than three codepoints of lookahead.';
return codepoint(i + num);
};
const consume = function (num) {
if (num === undefined) num = 1;
i += num;
code = codepoint(i);
if (newline(code)) incrLineno();else column += num;
// console.log('Consume '+i+' '+String.fromCharCode(code) + ' 0x' + code.toString(16));
return true;
};
const reconsume = function () {
i -= 1;
if (newline(code)) {
line -= 1;
column = lastLineLength;
} else {
column -= 1;
}
locStart.line = line;
locStart.column = column;
return true;
};
const eof = function (codepoint) {
if (codepoint === undefined) codepoint = code;
return codepoint === -1;
};
const donothing = function () {};
const parseerror = function () {
// Language bindings don't like writing to stdout!
// console.log('Parse error at index ' + i + ', processing codepoint 0x' + code.toString(16) + '.'); return true;
};
const consumeAToken = function () {
consumeComments();
consume();
if (whitespace(code)) {
while (whitespace(next())) consume();
return new WhitespaceToken();
} else if (code === 0x22) {
return consumeAStringToken();
} else if (code === 0x23) {
if (namechar(next()) || areAValidEscape(next(1), next(2))) {
const token = new HashToken('');
if (wouldStartAnIdentifier(next(1), next(2), next(3))) token.type = 'id';
token.value = consumeAName();
return token;
} else {
return new DelimToken(code);
}
} else if (code === 0x24) {
if (next() === 0x3d) {
consume();
return new SuffixMatchToken();
} else {
return new DelimToken(code);
}
} else if (code === 0x27) {
return consumeAStringToken();
} else if (code === 0x28) {
return new OpenParenToken();
} else if (code === 0x29) {
return new CloseParenToken();
} else if (code === 0x2a) {
if (next() === 0x3d) {
consume();
return new SubstringMatchToken();
} else {
return new DelimToken(code);
}
} else if (code === 0x2b) {
if (startsWithANumber()) {
reconsume();
return consumeANumericToken();
} else {
return new DelimToken(code);
}
} else if (code === 0x2c) {
return new CommaToken();
} else if (code === 0x2d) {
if (startsWithANumber()) {
reconsume();
return consumeANumericToken();
} else if (next(1) === 0x2d && next(2) === 0x3e) {
consume(2);
return new CDCToken();
} else if (startsWithAnIdentifier()) {
reconsume();
return consumeAnIdentlikeToken();
} else {
return new DelimToken(code);
}
} else if (code === 0x2e) {
if (startsWithANumber()) {
reconsume();
return consumeANumericToken();
} else {
return new DelimToken(code);
}
} else if (code === 0x3a) {
return new ColonToken();
} else if (code === 0x3b) {
return new SemicolonToken();
} else if (code === 0x3c) {
if (next(1) === 0x21 && next(2) === 0x2d && next(3) === 0x2d) {
consume(3);
return new CDOToken();
} else {
return new DelimToken(code);
}
} else if (code === 0x40) {
if (wouldStartAnIdentifier(next(1), next(2), next(3))) return new AtKeywordToken(consumeAName());else return new DelimToken(code);
} else if (code === 0x5b) {
return new OpenSquareToken();
} else if (code === 0x5c) {
if (startsWithAValidEscape()) {
reconsume();
return consumeAnIdentlikeToken();
} else {
parseerror();
return new DelimToken(code);
}
} else if (code === 0x5d) {
return new CloseSquareToken();
} else if (code === 0x5e) {
if (next() === 0x3d) {
consume();
return new PrefixMatchToken();
} else {
return new DelimToken(code);
}
} else if (code === 0x7b) {
return new OpenCurlyToken();
} else if (code === 0x7c) {
if (next() === 0x3d) {
consume();
return new DashMatchToken();
} else if (next() === 0x7c) {
consume();
return new ColumnToken();
} else {
return new DelimToken(code);
}
} else if (code === 0x7d) {
return new CloseCurlyToken();
} else if (code === 0x7e) {
if (next() === 0x3d) {
consume();
return new IncludeMatchToken();
} else {
return new DelimToken(code);
}
} else if (digit(code)) {
reconsume();
return consumeANumericToken();
} else if (namestartchar(code)) {
reconsume();
return consumeAnIdentlikeToken();
} else if (eof()) {
return new EOFToken();
} else {
return new DelimToken(code);
}
};
const consumeComments = function () {
while (next(1) === 0x2f && next(2) === 0x2a) {
consume(2);
while (true) {
consume();
if (code === 0x2a && next() === 0x2f) {
consume();
break;
} else if (eof()) {
parseerror();
return;
}
}
}
};
const consumeANumericToken = function () {
const num = consumeANumber();
if (wouldStartAnIdentifier(next(1), next(2), next(3))) {
const token = new DimensionToken();
token.value = num.value;
token.repr = num.repr;
token.type = num.type;
token.unit = consumeAName();
return token;
} else if (next() === 0x25) {
consume();
const token = new PercentageToken();
token.value = num.value;
token.repr = num.repr;
return token;
} else {
const token = new NumberToken();
token.value = num.value;
token.repr = num.repr;
token.type = num.type;
return token;
}
};
const consumeAnIdentlikeToken = function () {
const str = consumeAName();
if (str.toLowerCase() === 'url' && next() === 0x28) {
consume();
while (whitespace(next(1)) && whitespace(next(2))) consume();
if (next() === 0x22 || next() === 0x27) return new FunctionToken(str);else if (whitespace(next()) && (next(2) === 0x22 || next(2) === 0x27)) return new FunctionToken(str);else return consumeAURLToken();
} else if (next() === 0x28) {
consume();
return new FunctionToken(str);
} else {
return new IdentToken(str);
}
};
const consumeAStringToken = function (endingCodePoint) {
if (endingCodePoint === undefined) endingCodePoint = code;
let string = '';
while (consume()) {
if (code === endingCodePoint || eof()) {
return new StringToken(string);
} else if (newline(code)) {
parseerror();
reconsume();
return new BadStringToken();
} else if (code === 0x5c) {
if (eof(next())) donothing();else if (newline(next())) consume();else string += stringFromCode(consumeEscape());
} else {
string += stringFromCode(code);
}
}
throw new Error('Internal error');
};
const consumeAURLToken = function () {
const token = new URLToken('');
while (whitespace(next())) consume();
if (eof(next())) return token;
while (consume()) {
if (code === 0x29 || eof()) {
return token;
} else if (whitespace(code)) {
while (whitespace(next())) consume();
if (next() === 0x29 || eof(next())) {
consume();
return token;
} else {
consumeTheRemnantsOfABadURL();
return new BadURLToken();
}
} else if (code === 0x22 || code === 0x27 || code === 0x28 || nonprintable(code)) {
parseerror();
consumeTheRemnantsOfABadURL();
return new BadURLToken();
} else if (code === 0x5c) {
if (startsWithAValidEscape()) {
token.value += stringFromCode(consumeEscape());
} else {
parseerror();
consumeTheRemnantsOfABadURL();
return new BadURLToken();
}
} else {
token.value += stringFromCode(code);
}
}
throw new Error('Internal error');
};
const consumeEscape = function () {
// Assume the the current character is the \
// and the next code point is not a newline.
consume();
if (hexdigit(code)) {
// Consume 1-6 hex digits
const digits = [code];
for (let total = 0; total < 5; total++) {
if (hexdigit(next())) {
consume();
digits.push(code);
} else {
break;
}
}
if (whitespace(next())) consume();
let value = parseInt(digits.map(function (x) {
return String.fromCharCode(x);
}).join(''), 16);
if (value > maximumallowedcodepoint) value = 0xfffd;
return value;
} else if (eof()) {
return 0xfffd;
} else {
return code;
}
};
const areAValidEscape = function (c1, c2) {
if (c1 !== 0x5c) return false;
if (newline(c2)) return false;
return true;
};
const startsWithAValidEscape = function () {
return areAValidEscape(code, next());
};
const wouldStartAnIdentifier = function (c1, c2, c3) {
if (c1 === 0x2d) return namestartchar(c2) || c2 === 0x2d || areAValidEscape(c2, c3);else if (namestartchar(c1)) return true;else if (c1 === 0x5c) return areAValidEscape(c1, c2);else return false;
};
const startsWithAnIdentifier = function () {
return wouldStartAnIdentifier(code, next(1), next(2));
};
const wouldStartANumber = function (c1, c2, c3) {
if (c1 === 0x2b || c1 === 0x2d) {
if (digit(c2)) return true;
if (c2 === 0x2e && digit(c3)) return true;
return false;
} else if (c1 === 0x2e) {
if (digit(c2)) return true;
return false;
} else if (digit(c1)) {
return true;
} else {
return false;
}
};
const startsWithANumber = function () {
return wouldStartANumber(code, next(1), next(2));
};
const consumeAName = function () {
let result = '';
while (consume()) {
if (namechar(code)) {
result += stringFromCode(code);
} else if (startsWithAValidEscape()) {
result += stringFromCode(consumeEscape());
} else {
reconsume();
return result;
}
}
throw new Error('Internal parse error');
};
const consumeANumber = function () {
let repr = '';
let type = 'integer';
if (next() === 0x2b || next() === 0x2d) {
consume();
repr += stringFromCode(code);
}
while (digit(next())) {
consume();
repr += stringFromCode(code);
}
if (next(1) === 0x2e && digit(next(2))) {
consume();
repr += stringFromCode(code);
consume();
repr += stringFromCode(code);
type = 'number';
while (digit(next())) {
consume();
repr += stringFromCode(code);
}
}
const c1 = next(1),
c2 = next(2),
c3 = next(3);
if ((c1 === 0x45 || c1 === 0x65) && digit(c2)) {
consume();
repr += stringFromCode(code);
consume();
repr += stringFromCode(code);
type = 'number';
while (digit(next())) {
consume();
repr += stringFromCode(code);
}
} else if ((c1 === 0x45 || c1 === 0x65) && (c2 === 0x2b || c2 === 0x2d) && digit(c3)) {
consume();
repr += stringFromCode(code);
consume();
repr += stringFromCode(code);
consume();
repr += stringFromCode(code);
type = 'number';
while (digit(next())) {
consume();
repr += stringFromCode(code);
}
}
const value = convertAStringToANumber(repr);
return {
type: type,
value: value,
repr: repr
};
};
const convertAStringToANumber = function (string) {
// CSS's number rules are identical to JS, afaik.
return +string;
};
const consumeTheRemnantsOfABadURL = function () {
while (consume()) {
if (code === 0x29 || eof()) {
return;
} else if (startsWithAValidEscape()) {
consumeEscape();
donothing();
} else {
donothing();
}
}
};
let iterationCount = 0;
while (!eof(next())) {
tokens.push(consumeAToken());
iterationCount++;
if (iterationCount > str.length * 2) throw new Error("I'm infinite-looping!");
}
return tokens;
}
class CSSParserToken {
constructor() {
this.tokenType = '';
this.value = void 0;
}
toJSON() {
return {
token: this.tokenType
};
}
toString() {
return this.tokenType;
}
toSource() {
return '' + this;
}
}
exports.CSSParserToken = CSSParserToken;
class BadStringToken extends CSSParserToken {
constructor(...args) {
super(...args);
this.tokenType = 'BADSTRING';
}
}
exports.BadStringToken = BadStringToken;
class BadURLToken extends CSSParserToken {
constructor(...args) {
super(...args);
this.tokenType = 'BADURL';
}
}
exports.BadURLToken = BadURLToken;
class WhitespaceToken extends CSSParserToken {
constructor(...args) {
super(...args);
this.tokenType = 'WHITESPACE';
}
toString() {
return 'WS';
}
toSource() {
return ' ';
}
}
exports.WhitespaceToken = WhitespaceToken;
class CDOToken extends CSSParserToken {
constructor(...args) {
super(...args);
this.tokenType = 'CDO';
}
toSource() {
return '<!--';
}
}
exports.CDOToken = CDOToken;
class CDCToken extends CSSParserToken {
constructor(...args) {
super(...args);
this.tokenType = 'CDC';
}
toSource() {
return '-->';
}
}
exports.CDCToken = CDCToken;
class ColonToken extends CSSParserToken {
constructor(...args) {
super(...args);
this.tokenType = ':';
}
}
exports.ColonToken = ColonToken;
class SemicolonToken extends CSSParserToken {
constructor(...args) {
super(...args);
this.tokenType = ';';
}
}
exports.SemicolonToken = SemicolonToken;
class CommaToken extends CSSParserToken {
constructor(...args) {
super(...args);
this.tokenType = ',';
}
}
exports.CommaToken = CommaToken;
class GroupingToken extends CSSParserToken {
constructor(...args) {
super(...args);
this.value = '';
this.mirror = '';
}
}
exports.GroupingToken = GroupingToken;
class OpenCurlyToken extends GroupingToken {
constructor() {
super();
this.tokenType = '{';
this.value = '{';
this.mirror = '}';
}
}
exports.OpenCurlyToken = OpenCurlyToken;
class CloseCurlyToken extends GroupingToken {
constructor() {
super();
this.tokenType = '}';
this.value = '}';
this.mirror = '{';
}
}
exports.CloseCurlyToken = CloseCurlyToken;
class OpenSquareToken extends GroupingToken {
constructor() {
super();
this.tokenType = '[';
this.value = '[';
this.mirror = ']';
}
}
exports.OpenSquareToken = OpenSquareToken;
class CloseSquareToken extends GroupingToken {
constructor() {
super();
this.tokenType = ']';
this.value = ']';
this.mirror = '[';
}
}
exports.CloseSquareToken = CloseSquareToken;
class OpenParenToken extends GroupingToken {
constructor() {
super();
this.tokenType = '(';
this.value = '(';
this.mirror = ')';
}
}
exports.OpenParenToken = OpenParenToken;
class CloseParenToken extends GroupingToken {
constructor() {
super();
this.tokenType = ')';
this.value = ')';
this.mirror = '(';
}
}
exports.CloseParenToken = CloseParenToken;
class IncludeMatchToken extends CSSParserToken {
constructor(...args) {
super(...args);
this.tokenType = '~=';
}
}
exports.IncludeMatchToken = IncludeMatchToken;
class DashMatchToken extends CSSParserToken {
constructor(...args) {
super(...args);
this.tokenType = '|=';
}
}
exports.DashMatchToken = DashMatchToken;
class PrefixMatchToken extends CSSParserToken {
constructor(...args) {
super(...args);
this.tokenType = '^=';
}
}
exports.PrefixMatchToken = PrefixMatchToken;
class SuffixMatchToken extends CSSParserToken {
constructor(...args) {
super(...args);
this.tokenType = '$=';
}
}
exports.SuffixMatchToken = SuffixMatchToken;
class SubstringMatchToken extends CSSParserToken {
constructor(...args) {
super(...args);
this.tokenType = '*=';
}
}
exports.SubstringMatchToken = SubstringMatchToken;
class ColumnToken extends CSSParserToken {
constructor(...args) {
super(...args);
this.tokenType = '||';
}
}
exports.ColumnToken = ColumnToken;
class EOFToken extends CSSParserToken {
constructor(...args) {
super(...args);
this.tokenType = 'EOF';
}
toSource() {
return '';
}
}
exports.EOFToken = EOFToken;
class DelimToken extends CSSParserToken {
constructor(code) {
super();
this.tokenType = 'DELIM';
this.value = '';
this.value = stringFromCode(code);
}
toString() {
return 'DELIM(' + this.value + ')';
}
toJSON() {
const json = this.constructor.prototype.constructor.prototype.toJSON.call(this);
json.value = this.value;
return json;
}
toSource() {
if (this.value === '\\') return '\\\n';else return this.value;
}
}
exports.DelimToken = DelimToken;
class StringValuedToken extends CSSParserToken {
constructor(...args) {
super(...args);
this.value = '';
}
ASCIIMatch(str) {
return this.value.toLowerCase() === str.toLowerCase();
}
toJSON() {
const json = this.constructor.prototype.constructor.prototype.toJSON.call(this);
json.value = this.value;
return json;
}
}
exports.StringValuedToken = StringValuedToken;
class IdentToken extends StringValuedToken {
constructor(val) {
super();
this.tokenType = 'IDENT';
this.value = val;
}
toString() {
return 'IDENT(' + this.value + ')';
}
toSource() {
return escapeIdent(this.value);
}
}
exports.IdentToken = IdentToken;
class FunctionToken extends StringValuedToken {
constructor(val) {
super();
this.tokenType = 'FUNCTION';
this.mirror = void 0;
this.value = val;
this.mirror = ')';
}
toString() {
return 'FUNCTION(' + this.value + ')';
}
toSource() {
return escapeIdent(this.value) + '(';
}
}
exports.FunctionToken = FunctionToken;
class AtKeywordToken extends StringValuedToken {
constructor(val) {
super();
this.tokenType = 'AT-KEYWORD';
this.value = val;
}
toString() {
return 'AT(' + this.value + ')';
}
toSource() {
return '@' + escapeIdent(this.value);
}
}
exports.AtKeywordToken = AtKeywordToken;
class HashToken extends StringValuedToken {
constructor(val) {
super();
this.tokenType = 'HASH';
this.type = void 0;
this.value = val;
this.type = 'unrestricted';
}
toString() {
return 'HASH(' + this.value + ')';
}
toJSON() {
const json = this.constructor.prototype.constructor.prototype.toJSON.call(this);
json.value = this.value;
json.type = this.type;
return json;
}
toSource() {
if (this.type === 'id') return '#' + escapeIdent(this.value);else return '#' + escapeHash(this.value);
}
}
exports.HashToken = HashToken;
class StringToken extends StringValuedToken {
constructor(val) {
super();
this.tokenType = 'STRING';
this.value = val;
}
toString() {
return '"' + escapeString(this.value) + '"';
}
}
exports.StringToken = StringToken;
class URLToken extends StringValuedToken {
constructor(val) {
super();
this.tokenType = 'URL';
this.value = val;
}
toString() {
return 'URL(' + this.value + ')';
}
toSource() {
return 'url("' + escapeString(this.value) + '")';
}
}
exports.URLToken = URLToken;
class NumberToken extends CSSParserToken {
constructor() {
super();
this.tokenType = 'NUMBER';
this.type = void 0;
this.repr = void 0;
this.type = 'integer';
this.repr = '';
}
toString() {
if (this.type === 'integer') return 'INT(' + this.value + ')';
return 'NUMBER(' + this.value + ')';
}
toJSON() {
const json = super.toJSON();
json.value = this.value;
json.type = this.type;
json.repr = this.repr;
return json;
}
toSource() {
return this.repr;
}
}
exports.NumberToken = NumberToken;
class PercentageToken extends CSSParserToken {
constructor() {
super();
this.tokenType = 'PERCENTAGE';
this.repr = void 0;
this.repr = '';
}
toString() {
return 'PERCENTAGE(' + this.value + ')';
}
toJSON() {
const json = this.constructor.prototype.constructor.prototype.toJSON.call(this);
json.value = this.value;
json.repr = this.repr;
return json;
}
toSource() {
return this.repr + '%';
}
}
exports.PercentageToken = PercentageToken;
class DimensionToken extends CSSParserToken {
constructor() {
super();
this.tokenType = 'DIMENSION';
this.type = void 0;
this.repr = void 0;
this.unit = void 0;
this.type = 'integer';
this.repr = '';
this.unit = '';
}
toString() {
return 'DIM(' + this.value + ',' + this.unit + ')';
}
toJSON() {
const json = this.constructor.prototype.constructor.prototype.toJSON.call(this);
json.value = this.value;
json.type = this.type;
json.repr = this.repr;
json.unit = this.unit;
return json;
}
toSource() {
const source = this.repr;
let unit = escapeIdent(this.unit);
if (unit[0].toLowerCase() === 'e' && (unit[1] === '-' || between(unit.charCodeAt(1), 0x30, 0x39))) {
// Unit is ambiguous with scinot
// Remove the leading "e", replace with escape.
unit = '\\65 ' + unit.slice(1, unit.length);
}
return source + unit;
}
}
exports.DimensionToken = DimensionToken;
function escapeIdent(string) {
string = '' + string;
let result = '';
const firstcode = string.charCodeAt(0);
for (let i = 0; i < string.length; i++) {
const code = string.charCodeAt(i);
if (code === 0x0) throw new InvalidCharacterError('Invalid character: the input contains U+0000.');
if (between(code, 0x1, 0x1f) || code === 0x7f || i === 0 && between(code, 0x30, 0x39) || i === 1 && between(code, 0x30, 0x39) && firstcode === 0x2d) result += '\\' + code.toString(16) + ' ';else if (code >= 0x80 || code === 0x2d || code === 0x5f || between(code, 0x30, 0x39) || between(code, 0x41, 0x5a) || between(code, 0x61, 0x7a)) result += string[i];else result += '\\' + string[i];
}
return result;
}
function escapeHash(string) {
// Escapes the contents of "unrestricted"-type hash tokens.
// Won't preserve the ID-ness of "id"-type hash tokens;
// use escapeIdent() for that.
string = '' + string;
let result = '';
for (let i = 0; i < string.length; i++) {
const code = string.charCodeAt(i);
if (code === 0x0) throw new InvalidCharacterError('Invalid character: the input contains U+0000.');
if (code >= 0x80 || code === 0x2d || code === 0x5f || between(code, 0x30, 0x39) || between(code, 0x41, 0x5a) || between(code, 0x61, 0x7a)) result += string[i];else result += '\\' + code.toString(16) + ' ';
}
return result;
}
function escapeString(string) {
string = '' + string;
let result = '';
for (let i = 0; i < string.length; i++) {
const code = string.charCodeAt(i);
if (code === 0x0) throw new InvalidCharacterError('Invalid character: the input contains U+0000.');
if (between(code, 0x1, 0x1f) || code === 0x7f) result += '\\' + code.toString(16) + ' ';else if (code === 0x22 || code === 0x5c) result += '\\' + string[i];else result += string[i];
}
return result;
}

View File

@@ -0,0 +1,626 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.PythonLocatorFactory = exports.JsonlLocatorFactory = exports.JavaScriptLocatorFactory = exports.JavaLocatorFactory = exports.CSharpLocatorFactory = void 0;
exports.asLocator = asLocator;
exports.asLocators = asLocators;
var _stringUtils = require("./stringUtils");
var _selectorParser = require("./selectorParser");
/**
* Copyright (c) Microsoft Corporation.
*
* 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.
*/
function asLocator(lang, selector, isFrameLocator = false, playSafe = false) {
return asLocators(lang, selector, isFrameLocator, playSafe)[0];
}
function asLocators(lang, selector, isFrameLocator = false, playSafe = false, maxOutputSize = 20) {
if (playSafe) {
try {
return innerAsLocators(generators[lang], (0, _selectorParser.parseSelector)(selector), isFrameLocator, maxOutputSize);
} catch (e) {
// Tolerate invalid input.
return [selector];
}
} else {
return innerAsLocators(generators[lang], (0, _selectorParser.parseSelector)(selector), isFrameLocator, maxOutputSize);
}
}
function innerAsLocators(factory, parsed, isFrameLocator = false, maxOutputSize = 20) {
const parts = [...parsed.parts];
// frameLocator('iframe').first is actually "iframe >> nth=0 >> internal:control=enter-frame"
// To make it easier to parse, we turn it into "iframe >> internal:control=enter-frame >> nth=0"
for (let index = 0; index < parts.length - 1; index++) {
if (parts[index].name === 'nth' && parts[index + 1].name === 'internal:control' && parts[index + 1].body === 'enter-frame') {
// Swap nth and enter-frame.
const [nth] = parts.splice(index, 1);
parts.splice(index + 1, 0, nth);
}
}
const tokens = [];
let nextBase = isFrameLocator ? 'frame-locator' : 'page';
for (let index = 0; index < parts.length; index++) {
const part = parts[index];
const base = nextBase;
nextBase = 'locator';
if (part.name === 'nth') {
if (part.body === '0') tokens.push([factory.generateLocator(base, 'first', ''), factory.generateLocator(base, 'nth', '0')]);else if (part.body === '-1') tokens.push([factory.generateLocator(base, 'last', ''), factory.generateLocator(base, 'nth', '-1')]);else tokens.push([factory.generateLocator(base, 'nth', part.body)]);
continue;
}
if (part.name === 'internal:text') {
const {
exact,
text
} = detectExact(part.body);
tokens.push([factory.generateLocator(base, 'text', text, {
exact
})]);
continue;
}
if (part.name === 'internal:has-text') {
const {
exact,
text
} = detectExact(part.body);
// There is no locator equivalent for strict has-text, leave it as is.
if (!exact) {
tokens.push([factory.generateLocator(base, 'has-text', text, {
exact
})]);
continue;
}
}
if (part.name === 'internal:has-not-text') {
const {
exact,
text
} = detectExact(part.body);
// There is no locator equivalent for strict has-not-text, leave it as is.
if (!exact) {
tokens.push([factory.generateLocator(base, 'has-not-text', text, {
exact
})]);
continue;
}
}
if (part.name === 'internal:has') {
const inners = innerAsLocators(factory, part.body.parsed, false, maxOutputSize);
tokens.push(inners.map(inner => factory.generateLocator(base, 'has', inner)));
continue;
}
if (part.name === 'internal:has-not') {
const inners = innerAsLocators(factory, part.body.parsed, false, maxOutputSize);
tokens.push(inners.map(inner => factory.generateLocator(base, 'hasNot', inner)));
continue;
}
if (part.name === 'internal:and') {
const inners = innerAsLocators(factory, part.body.parsed, false, maxOutputSize);
tokens.push(inners.map(inner => factory.generateLocator(base, 'and', inner)));
continue;
}
if (part.name === 'internal:or') {
const inners = innerAsLocators(factory, part.body.parsed, false, maxOutputSize);
tokens.push(inners.map(inner => factory.generateLocator(base, 'or', inner)));
continue;
}
if (part.name === 'internal:label') {
const {
exact,
text
} = detectExact(part.body);
tokens.push([factory.generateLocator(base, 'label', text, {
exact
})]);
continue;
}
if (part.name === 'internal:role') {
const attrSelector = (0, _selectorParser.parseAttributeSelector)(part.body, true);
const options = {
attrs: []
};
for (const attr of attrSelector.attributes) {
if (attr.name === 'name') {
options.exact = attr.caseSensitive;
options.name = attr.value;
} else {
if (attr.name === 'level' && typeof attr.value === 'string') attr.value = +attr.value;
options.attrs.push({
name: attr.name === 'include-hidden' ? 'includeHidden' : attr.name,
value: attr.value
});
}
}
tokens.push([factory.generateLocator(base, 'role', attrSelector.name, options)]);
continue;
}
if (part.name === 'internal:testid') {
const attrSelector = (0, _selectorParser.parseAttributeSelector)(part.body, true);
const {
value
} = attrSelector.attributes[0];
tokens.push([factory.generateLocator(base, 'test-id', value)]);
continue;
}
if (part.name === 'internal:attr') {
const attrSelector = (0, _selectorParser.parseAttributeSelector)(part.body, true);
const {
name,
value,
caseSensitive
} = attrSelector.attributes[0];
const text = value;
const exact = !!caseSensitive;
if (name === 'placeholder') {
tokens.push([factory.generateLocator(base, 'placeholder', text, {
exact
})]);
continue;
}
if (name === 'alt') {
tokens.push([factory.generateLocator(base, 'alt', text, {
exact
})]);
continue;
}
if (name === 'title') {
tokens.push([factory.generateLocator(base, 'title', text, {
exact
})]);
continue;
}
}
let locatorType = 'default';
const nextPart = parts[index + 1];
if (nextPart && nextPart.name === 'internal:control' && nextPart.body === 'enter-frame') {
locatorType = 'frame';
nextBase = 'frame-locator';
index++;
}
const selectorPart = (0, _selectorParser.stringifySelector)({
parts: [part]
});
const locatorPart = factory.generateLocator(base, locatorType, selectorPart);
if (locatorType === 'default' && nextPart && ['internal:has-text', 'internal:has-not-text'].includes(nextPart.name)) {
const {
exact,
text
} = detectExact(nextPart.body);
// There is no locator equivalent for strict has-text and has-not-text, leave it as is.
if (!exact) {
const nextLocatorPart = factory.generateLocator('locator', nextPart.name === 'internal:has-text' ? 'has-text' : 'has-not-text', text, {
exact
});
const options = {};
if (nextPart.name === 'internal:has-text') options.hasText = text;else options.hasNotText = text;
const combinedPart = factory.generateLocator(base, 'default', selectorPart, options);
// Two options:
// - locator('div').filter({ hasText: 'foo' })
// - locator('div', { hasText: 'foo' })
tokens.push([factory.chainLocators([locatorPart, nextLocatorPart]), combinedPart]);
index++;
continue;
}
}
tokens.push([locatorPart]);
}
return combineTokens(factory, tokens, maxOutputSize);
}
function combineTokens(factory, tokens, maxOutputSize) {
const currentTokens = tokens.map(() => '');
const result = [];
const visit = index => {
if (index === tokens.length) {
result.push(factory.chainLocators(currentTokens));
return currentTokens.length < maxOutputSize;
}
for (const taken of tokens[index]) {
currentTokens[index] = taken;
if (!visit(index + 1)) return false;
}
return true;
};
visit(0);
return result;
}
function detectExact(text) {
let exact = false;
const match = text.match(/^\/(.*)\/([igm]*)$/);
if (match) return {
text: new RegExp(match[1], match[2])
};
if (text.endsWith('"')) {
text = JSON.parse(text);
exact = true;
} else if (text.endsWith('"s')) {
text = JSON.parse(text.substring(0, text.length - 1));
exact = true;
} else if (text.endsWith('"i')) {
text = JSON.parse(text.substring(0, text.length - 1));
exact = false;
}
return {
exact,
text
};
}
class JavaScriptLocatorFactory {
generateLocator(base, kind, body, options = {}) {
switch (kind) {
case 'default':
if (options.hasText !== undefined) return `locator(${this.quote(body)}, { hasText: ${this.toHasText(options.hasText)} })`;
if (options.hasNotText !== undefined) return `locator(${this.quote(body)}, { hasNotText: ${this.toHasText(options.hasNotText)} })`;
return `locator(${this.quote(body)})`;
case 'frame':
return `frameLocator(${this.quote(body)})`;
case 'nth':
return `nth(${body})`;
case 'first':
return `first()`;
case 'last':
return `last()`;
case 'role':
const attrs = [];
if (isRegExp(options.name)) {
attrs.push(`name: ${options.name}`);
} else if (typeof options.name === 'string') {
attrs.push(`name: ${this.quote(options.name)}`);
if (options.exact) attrs.push(`exact: true`);
}
for (const {
name,
value
} of options.attrs) attrs.push(`${name}: ${typeof value === 'string' ? this.quote(value) : value}`);
const attrString = attrs.length ? `, { ${attrs.join(', ')} }` : '';
return `getByRole(${this.quote(body)}${attrString})`;
case 'has-text':
return `filter({ hasText: ${this.toHasText(body)} })`;
case 'has-not-text':
return `filter({ hasNotText: ${this.toHasText(body)} })`;
case 'has':
return `filter({ has: ${body} })`;
case 'hasNot':
return `filter({ hasNot: ${body} })`;
case 'and':
return `and(${body})`;
case 'or':
return `or(${body})`;
case 'test-id':
return `getByTestId(${this.toTestIdValue(body)})`;
case 'text':
return this.toCallWithExact('getByText', body, !!options.exact);
case 'alt':
return this.toCallWithExact('getByAltText', body, !!options.exact);
case 'placeholder':
return this.toCallWithExact('getByPlaceholder', body, !!options.exact);
case 'label':
return this.toCallWithExact('getByLabel', body, !!options.exact);
case 'title':
return this.toCallWithExact('getByTitle', body, !!options.exact);
default:
throw new Error('Unknown selector kind ' + kind);
}
}
chainLocators(locators) {
return locators.join('.');
}
toCallWithExact(method, body, exact) {
if (isRegExp(body)) return `${method}(${body})`;
return exact ? `${method}(${this.quote(body)}, { exact: true })` : `${method}(${this.quote(body)})`;
}
toHasText(body) {
if (isRegExp(body)) return String(body);
return this.quote(body);
}
toTestIdValue(value) {
if (isRegExp(value)) return String(value);
return this.quote(value);
}
quote(text) {
return (0, _stringUtils.escapeWithQuotes)(text, '\'');
}
}
exports.JavaScriptLocatorFactory = JavaScriptLocatorFactory;
class PythonLocatorFactory {
generateLocator(base, kind, body, options = {}) {
switch (kind) {
case 'default':
if (options.hasText !== undefined) return `locator(${this.quote(body)}, has_text=${this.toHasText(options.hasText)})`;
if (options.hasNotText !== undefined) return `locator(${this.quote(body)}, has_not_text=${this.toHasText(options.hasNotText)})`;
return `locator(${this.quote(body)})`;
case 'frame':
return `frame_locator(${this.quote(body)})`;
case 'nth':
return `nth(${body})`;
case 'first':
return `first`;
case 'last':
return `last`;
case 'role':
const attrs = [];
if (isRegExp(options.name)) {
attrs.push(`name=${this.regexToString(options.name)}`);
} else if (typeof options.name === 'string') {
attrs.push(`name=${this.quote(options.name)}`);
if (options.exact) attrs.push(`exact=True`);
}
for (const {
name,
value
} of options.attrs) {
let valueString = typeof value === 'string' ? this.quote(value) : value;
if (typeof value === 'boolean') valueString = value ? 'True' : 'False';
attrs.push(`${(0, _stringUtils.toSnakeCase)(name)}=${valueString}`);
}
const attrString = attrs.length ? `, ${attrs.join(', ')}` : '';
return `get_by_role(${this.quote(body)}${attrString})`;
case 'has-text':
return `filter(has_text=${this.toHasText(body)})`;
case 'has-not-text':
return `filter(has_not_text=${this.toHasText(body)})`;
case 'has':
return `filter(has=${body})`;
case 'hasNot':
return `filter(has_not=${body})`;
case 'and':
return `and_(${body})`;
case 'or':
return `or_(${body})`;
case 'test-id':
return `get_by_test_id(${this.toTestIdValue(body)})`;
case 'text':
return this.toCallWithExact('get_by_text', body, !!options.exact);
case 'alt':
return this.toCallWithExact('get_by_alt_text', body, !!options.exact);
case 'placeholder':
return this.toCallWithExact('get_by_placeholder', body, !!options.exact);
case 'label':
return this.toCallWithExact('get_by_label', body, !!options.exact);
case 'title':
return this.toCallWithExact('get_by_title', body, !!options.exact);
default:
throw new Error('Unknown selector kind ' + kind);
}
}
chainLocators(locators) {
return locators.join('.');
}
regexToString(body) {
const suffix = body.flags.includes('i') ? ', re.IGNORECASE' : '';
return `re.compile(r"${body.source.replace(/\\\//, '/').replace(/"/g, '\\"')}"${suffix})`;
}
toCallWithExact(method, body, exact) {
if (isRegExp(body)) return `${method}(${this.regexToString(body)})`;
if (exact) return `${method}(${this.quote(body)}, exact=True)`;
return `${method}(${this.quote(body)})`;
}
toHasText(body) {
if (isRegExp(body)) return this.regexToString(body);
return `${this.quote(body)}`;
}
toTestIdValue(value) {
if (isRegExp(value)) return this.regexToString(value);
return this.quote(value);
}
quote(text) {
return (0, _stringUtils.escapeWithQuotes)(text, '\"');
}
}
exports.PythonLocatorFactory = PythonLocatorFactory;
class JavaLocatorFactory {
generateLocator(base, kind, body, options = {}) {
let clazz;
switch (base) {
case 'page':
clazz = 'Page';
break;
case 'frame-locator':
clazz = 'FrameLocator';
break;
case 'locator':
clazz = 'Locator';
break;
}
switch (kind) {
case 'default':
if (options.hasText !== undefined) return `locator(${this.quote(body)}, new ${clazz}.LocatorOptions().setHasText(${this.toHasText(options.hasText)}))`;
if (options.hasNotText !== undefined) return `locator(${this.quote(body)}, new ${clazz}.LocatorOptions().setHasNotText(${this.toHasText(options.hasNotText)}))`;
return `locator(${this.quote(body)})`;
case 'frame':
return `frameLocator(${this.quote(body)})`;
case 'nth':
return `nth(${body})`;
case 'first':
return `first()`;
case 'last':
return `last()`;
case 'role':
const attrs = [];
if (isRegExp(options.name)) {
attrs.push(`.setName(${this.regexToString(options.name)})`);
} else if (typeof options.name === 'string') {
attrs.push(`.setName(${this.quote(options.name)})`);
if (options.exact) attrs.push(`.setExact(true)`);
}
for (const {
name,
value
} of options.attrs) attrs.push(`.set${(0, _stringUtils.toTitleCase)(name)}(${typeof value === 'string' ? this.quote(value) : value})`);
const attrString = attrs.length ? `, new ${clazz}.GetByRoleOptions()${attrs.join('')}` : '';
return `getByRole(AriaRole.${(0, _stringUtils.toSnakeCase)(body).toUpperCase()}${attrString})`;
case 'has-text':
return `filter(new ${clazz}.FilterOptions().setHasText(${this.toHasText(body)}))`;
case 'has-not-text':
return `filter(new ${clazz}.FilterOptions().setHasNotText(${this.toHasText(body)}))`;
case 'has':
return `filter(new ${clazz}.FilterOptions().setHas(${body}))`;
case 'hasNot':
return `filter(new ${clazz}.FilterOptions().setHasNot(${body}))`;
case 'and':
return `and(${body})`;
case 'or':
return `or(${body})`;
case 'test-id':
return `getByTestId(${this.toTestIdValue(body)})`;
case 'text':
return this.toCallWithExact(clazz, 'getByText', body, !!options.exact);
case 'alt':
return this.toCallWithExact(clazz, 'getByAltText', body, !!options.exact);
case 'placeholder':
return this.toCallWithExact(clazz, 'getByPlaceholder', body, !!options.exact);
case 'label':
return this.toCallWithExact(clazz, 'getByLabel', body, !!options.exact);
case 'title':
return this.toCallWithExact(clazz, 'getByTitle', body, !!options.exact);
default:
throw new Error('Unknown selector kind ' + kind);
}
}
chainLocators(locators) {
return locators.join('.');
}
regexToString(body) {
const suffix = body.flags.includes('i') ? ', Pattern.CASE_INSENSITIVE' : '';
return `Pattern.compile(${this.quote(body.source)}${suffix})`;
}
toCallWithExact(clazz, method, body, exact) {
if (isRegExp(body)) return `${method}(${this.regexToString(body)})`;
if (exact) return `${method}(${this.quote(body)}, new ${clazz}.${(0, _stringUtils.toTitleCase)(method)}Options().setExact(true))`;
return `${method}(${this.quote(body)})`;
}
toHasText(body) {
if (isRegExp(body)) return this.regexToString(body);
return this.quote(body);
}
toTestIdValue(value) {
if (isRegExp(value)) return this.regexToString(value);
return this.quote(value);
}
quote(text) {
return (0, _stringUtils.escapeWithQuotes)(text, '\"');
}
}
exports.JavaLocatorFactory = JavaLocatorFactory;
class CSharpLocatorFactory {
generateLocator(base, kind, body, options = {}) {
switch (kind) {
case 'default':
if (options.hasText !== undefined) return `Locator(${this.quote(body)}, new() { ${this.toHasText(options.hasText)} })`;
if (options.hasNotText !== undefined) return `Locator(${this.quote(body)}, new() { ${this.toHasNotText(options.hasNotText)} })`;
return `Locator(${this.quote(body)})`;
case 'frame':
return `FrameLocator(${this.quote(body)})`;
case 'nth':
return `Nth(${body})`;
case 'first':
return `First`;
case 'last':
return `Last`;
case 'role':
const attrs = [];
if (isRegExp(options.name)) {
attrs.push(`NameRegex = ${this.regexToString(options.name)}`);
} else if (typeof options.name === 'string') {
attrs.push(`Name = ${this.quote(options.name)}`);
if (options.exact) attrs.push(`Exact = true`);
}
for (const {
name,
value
} of options.attrs) attrs.push(`${(0, _stringUtils.toTitleCase)(name)} = ${typeof value === 'string' ? this.quote(value) : value}`);
const attrString = attrs.length ? `, new() { ${attrs.join(', ')} }` : '';
return `GetByRole(AriaRole.${(0, _stringUtils.toTitleCase)(body)}${attrString})`;
case 'has-text':
return `Filter(new() { ${this.toHasText(body)} })`;
case 'has-not-text':
return `Filter(new() { ${this.toHasNotText(body)} })`;
case 'has':
return `Filter(new() { Has = ${body} })`;
case 'hasNot':
return `Filter(new() { HasNot = ${body} })`;
case 'and':
return `And(${body})`;
case 'or':
return `Or(${body})`;
case 'test-id':
return `GetByTestId(${this.toTestIdValue(body)})`;
case 'text':
return this.toCallWithExact('GetByText', body, !!options.exact);
case 'alt':
return this.toCallWithExact('GetByAltText', body, !!options.exact);
case 'placeholder':
return this.toCallWithExact('GetByPlaceholder', body, !!options.exact);
case 'label':
return this.toCallWithExact('GetByLabel', body, !!options.exact);
case 'title':
return this.toCallWithExact('GetByTitle', body, !!options.exact);
default:
throw new Error('Unknown selector kind ' + kind);
}
}
chainLocators(locators) {
return locators.join('.');
}
regexToString(body) {
const suffix = body.flags.includes('i') ? ', RegexOptions.IgnoreCase' : '';
return `new Regex(${this.quote(body.source)}${suffix})`;
}
toCallWithExact(method, body, exact) {
if (isRegExp(body)) return `${method}(${this.regexToString(body)})`;
if (exact) return `${method}(${this.quote(body)}, new() { Exact = true })`;
return `${method}(${this.quote(body)})`;
}
toHasText(body) {
if (isRegExp(body)) return `HasTextRegex = ${this.regexToString(body)}`;
return `HasText = ${this.quote(body)}`;
}
toTestIdValue(value) {
if (isRegExp(value)) return this.regexToString(value);
return this.quote(value);
}
toHasNotText(body) {
if (isRegExp(body)) return `HasNotTextRegex = ${this.regexToString(body)}`;
return `HasNotText = ${this.quote(body)}`;
}
quote(text) {
return (0, _stringUtils.escapeWithQuotes)(text, '\"');
}
}
exports.CSharpLocatorFactory = CSharpLocatorFactory;
class JsonlLocatorFactory {
generateLocator(base, kind, body, options = {}) {
return JSON.stringify({
kind,
body,
options
});
}
chainLocators(locators) {
const objects = locators.map(l => JSON.parse(l));
for (let i = 0; i < objects.length - 1; ++i) objects[i].next = objects[i + 1];
return JSON.stringify(objects[0]);
}
}
exports.JsonlLocatorFactory = JsonlLocatorFactory;
const generators = {
javascript: new JavaScriptLocatorFactory(),
python: new PythonLocatorFactory(),
java: new JavaLocatorFactory(),
csharp: new CSharpLocatorFactory(),
jsonl: new JsonlLocatorFactory()
};
function isRegExp(obj) {
return obj instanceof RegExp;
}

View File

@@ -0,0 +1,170 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.locatorOrSelectorAsSelector = locatorOrSelectorAsSelector;
var _stringUtils = require("../../utils/isomorphic/stringUtils");
var _locatorGenerators = require("./locatorGenerators");
var _selectorParser = require("./selectorParser");
/**
* Copyright (c) Microsoft Corporation.
*
* 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.
*/
function parseLocator(locator, testIdAttributeName) {
locator = locator.replace(/AriaRole\s*\.\s*([\w]+)/g, (_, group) => group.toLowerCase()).replace(/(get_by_role|getByRole)\s*\(\s*(?:["'`])([^'"`]+)['"`]/g, (_, group1, group2) => `${group1}(${group2.toLowerCase()}`);
const params = [];
let template = '';
for (let i = 0; i < locator.length; ++i) {
const quote = locator[i];
if (quote !== '"' && quote !== '\'' && quote !== '`' && quote !== '/') {
template += quote;
continue;
}
const isRegexEscaping = locator[i - 1] === 'r' || locator[i] === '/';
++i;
let text = '';
while (i < locator.length) {
if (locator[i] === '\\') {
if (isRegexEscaping) {
if (locator[i + 1] !== quote) text += locator[i];
++i;
text += locator[i];
} else {
++i;
if (locator[i] === 'n') text += '\n';else if (locator[i] === 'r') text += '\r';else if (locator[i] === 't') text += '\t';else text += locator[i];
}
++i;
continue;
}
if (locator[i] !== quote) {
text += locator[i++];
continue;
}
break;
}
params.push({
quote,
text
});
template += (quote === '/' ? 'r' : '') + '$' + params.length;
}
// Equalize languages.
template = template.toLowerCase().replace(/get_by_alt_text/g, 'getbyalttext').replace(/get_by_test_id/g, 'getbytestid').replace(/get_by_([\w]+)/g, 'getby$1').replace(/has_not_text/g, 'hasnottext').replace(/has_text/g, 'hastext').replace(/has_not/g, 'hasnot').replace(/frame_locator/g, 'framelocator').replace(/[{}\s]/g, '').replace(/new\(\)/g, '').replace(/new[\w]+\.[\w]+options\(\)/g, '').replace(/\.set/g, ',set').replace(/\.or_\(/g, 'or(') // Python has "or_" instead of "or".
.replace(/\.and_\(/g, 'and(') // Python has "and_" instead of "and".
.replace(/:/g, '=').replace(/,re\.ignorecase/g, 'i').replace(/,pattern.case_insensitive/g, 'i').replace(/,regexoptions.ignorecase/g, 'i').replace(/re.compile\(([^)]+)\)/g, '$1') // Python has regex strings as r"foo"
.replace(/pattern.compile\(([^)]+)\)/g, 'r$1').replace(/newregex\(([^)]+)\)/g, 'r$1').replace(/string=/g, '=').replace(/regex=/g, '=').replace(/,,/g, ',');
return transform(template, params, testIdAttributeName);
}
function countParams(template) {
return [...template.matchAll(/\$\d+/g)].length;
}
function shiftParams(template, sub) {
return template.replace(/\$(\d+)/g, (_, ordinal) => `$${ordinal - sub}`);
}
function transform(template, params, testIdAttributeName) {
// Recursively handle filter(has=, hasnot=, sethas(), sethasnot()).
// TODO: handle and(locator), or(locator), locator(has=, hasnot=, sethas(), sethasnot()).
while (true) {
const hasMatch = template.match(/filter\(,?(has=|hasnot=|sethas\(|sethasnot\()/);
if (!hasMatch) break;
// Extract inner locator based on balanced parens.
const start = hasMatch.index + hasMatch[0].length;
let balance = 0;
let end = start;
for (; end < template.length; end++) {
if (template[end] === '(') balance++;else if (template[end] === ')') balance--;
if (balance < 0) break;
}
// Replace Java sethas(...) and sethasnot(...) with has=... and hasnot=...
let prefix = template.substring(0, start);
let extraSymbol = 0;
if (['sethas(', 'sethasnot('].includes(hasMatch[1])) {
// Eat extra ) symbol at the end of sethas(...)
extraSymbol = 1;
prefix = prefix.replace(/sethas\($/, 'has=').replace(/sethasnot\($/, 'hasnot=');
}
const paramsCountBeforeHas = countParams(template.substring(0, start));
const hasTemplate = shiftParams(template.substring(start, end), paramsCountBeforeHas);
const paramsCountInHas = countParams(hasTemplate);
const hasParams = params.slice(paramsCountBeforeHas, paramsCountBeforeHas + paramsCountInHas);
const hasSelector = JSON.stringify(transform(hasTemplate, hasParams, testIdAttributeName));
// Replace filter(has=...) with filter(has2=$5). Use has2 to avoid matching the same filter again.
// Replace filter(hasnot=...) with filter(hasnot2=$5). Use hasnot2 to avoid matching the same filter again.
template = prefix.replace(/=$/, '2=') + `$${paramsCountBeforeHas + 1}` + shiftParams(template.substring(end + extraSymbol), paramsCountInHas - 1);
// Replace inner params with $5 value.
const paramsBeforeHas = params.slice(0, paramsCountBeforeHas);
const paramsAfterHas = params.slice(paramsCountBeforeHas + paramsCountInHas);
params = paramsBeforeHas.concat([{
quote: '"',
text: hasSelector
}]).concat(paramsAfterHas);
}
// Transform to selector engines.
template = template.replace(/\,set([\w]+)\(([^)]+)\)/g, (_, group1, group2) => ',' + group1.toLowerCase() + '=' + group2.toLowerCase()).replace(/framelocator\(([^)]+)\)/g, '$1.internal:control=enter-frame').replace(/locator\(([^)]+),hastext=([^),]+)\)/g, 'locator($1).internal:has-text=$2').replace(/locator\(([^)]+),hasnottext=([^),]+)\)/g, 'locator($1).internal:has-not-text=$2').replace(/locator\(([^)]+),hastext=([^),]+)\)/g, 'locator($1).internal:has-text=$2').replace(/locator\(([^)]+)\)/g, '$1').replace(/getbyrole\(([^)]+)\)/g, 'internal:role=$1').replace(/getbytext\(([^)]+)\)/g, 'internal:text=$1').replace(/getbylabel\(([^)]+)\)/g, 'internal:label=$1').replace(/getbytestid\(([^)]+)\)/g, `internal:testid=[${testIdAttributeName}=$1]`).replace(/getby(placeholder|alt|title)(?:text)?\(([^)]+)\)/g, 'internal:attr=[$1=$2]').replace(/first(\(\))?/g, 'nth=0').replace(/last(\(\))?/g, 'nth=-1').replace(/nth\(([^)]+)\)/g, 'nth=$1').replace(/filter\(,?hastext=([^)]+)\)/g, 'internal:has-text=$1').replace(/filter\(,?hasnottext=([^)]+)\)/g, 'internal:has-not-text=$1').replace(/filter\(,?has2=([^)]+)\)/g, 'internal:has=$1').replace(/filter\(,?hasnot2=([^)]+)\)/g, 'internal:has-not=$1').replace(/,exact=false/g, '').replace(/,exact=true/g, 's').replace(/\,/g, '][');
const parts = template.split('.');
// Turn "internal:control=enter-frame >> nth=0" into "nth=0 >> internal:control=enter-frame"
// because these are swapped in locators vs selectors.
for (let index = 0; index < parts.length - 1; index++) {
if (parts[index] === 'internal:control=enter-frame' && parts[index + 1].startsWith('nth=')) {
// Swap nth and enter-frame.
const [nth] = parts.splice(index, 1);
parts.splice(index + 1, 0, nth);
}
}
// Substitute params.
return parts.map(t => {
if (!t.startsWith('internal:') || t === 'internal:control') return t.replace(/\$(\d+)/g, (_, ordinal) => {
const param = params[+ordinal - 1];
return param.text;
});
t = t.includes('[') ? t.replace(/\]/, '') + ']' : t;
t = t.replace(/(?:r)\$(\d+)(i)?/g, (_, ordinal, suffix) => {
const param = params[+ordinal - 1];
if (t.startsWith('internal:attr') || t.startsWith('internal:testid') || t.startsWith('internal:role')) return new RegExp(param.text) + (suffix || '');
return (0, _stringUtils.escapeForTextSelector)(new RegExp(param.text, suffix), false);
}).replace(/\$(\d+)(i|s)?/g, (_, ordinal, suffix) => {
const param = params[+ordinal - 1];
if (t.startsWith('internal:has=') || t.startsWith('internal:has-not=')) return param.text;
if (t.startsWith('internal:testid')) return (0, _stringUtils.escapeForAttributeSelector)(param.text, true);
if (t.startsWith('internal:attr') || t.startsWith('internal:role')) return (0, _stringUtils.escapeForAttributeSelector)(param.text, suffix === 's');
return (0, _stringUtils.escapeForTextSelector)(param.text, suffix === 's');
});
return t;
}).join(' >> ');
}
function locatorOrSelectorAsSelector(language, locator, testIdAttributeName) {
try {
(0, _selectorParser.parseSelector)(locator);
return locator;
} catch (e) {}
try {
const selector = parseLocator(locator, testIdAttributeName);
const locators = (0, _locatorGenerators.asLocators)(language, selector);
const digest = digestForComparison(locator);
if (locators.some(candidate => digestForComparison(candidate) === digest)) return selector;
} catch (e) {}
return '';
}
function digestForComparison(locator) {
return locator.replace(/\s/g, '').replace(/["`]/g, '\'');
}

View File

@@ -0,0 +1,64 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.getByAltTextSelector = getByAltTextSelector;
exports.getByLabelSelector = getByLabelSelector;
exports.getByPlaceholderSelector = getByPlaceholderSelector;
exports.getByRoleSelector = getByRoleSelector;
exports.getByTestIdSelector = getByTestIdSelector;
exports.getByTextSelector = getByTextSelector;
exports.getByTitleSelector = getByTitleSelector;
var _stringUtils = require("./stringUtils");
/**
* Copyright (c) Microsoft Corporation.
*
* 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.
*/
function getByAttributeTextSelector(attrName, text, options) {
if (!(0, _stringUtils.isString)(text)) return `internal:attr=[${attrName}=${text}]`;
return `internal:attr=[${attrName}=${(0, _stringUtils.escapeForAttributeSelector)(text, (options === null || options === void 0 ? void 0 : options.exact) || false)}]`;
}
function getByTestIdSelector(testIdAttributeName, testId) {
if (!(0, _stringUtils.isString)(testId)) return `internal:testid=[${testIdAttributeName}=${testId}]`;
return `internal:testid=[${testIdAttributeName}=${(0, _stringUtils.escapeForAttributeSelector)(testId, true)}]`;
}
function getByLabelSelector(text, options) {
return 'internal:label=' + (0, _stringUtils.escapeForTextSelector)(text, !!(options !== null && options !== void 0 && options.exact));
}
function getByAltTextSelector(text, options) {
return getByAttributeTextSelector('alt', text, options);
}
function getByTitleSelector(text, options) {
return getByAttributeTextSelector('title', text, options);
}
function getByPlaceholderSelector(text, options) {
return getByAttributeTextSelector('placeholder', text, options);
}
function getByTextSelector(text, options) {
return 'internal:text=' + (0, _stringUtils.escapeForTextSelector)(text, !!(options !== null && options !== void 0 && options.exact));
}
function getByRoleSelector(role, options = {}) {
const props = [];
if (options.checked !== undefined) props.push(['checked', String(options.checked)]);
if (options.disabled !== undefined) props.push(['disabled', String(options.disabled)]);
if (options.selected !== undefined) props.push(['selected', String(options.selected)]);
if (options.expanded !== undefined) props.push(['expanded', String(options.expanded)]);
if (options.includeHidden !== undefined) props.push(['include-hidden', String(options.includeHidden)]);
if (options.level !== undefined) props.push(['level', String(options.level)]);
if (options.name !== undefined) props.push(['name', (0, _stringUtils.isString)(options.name) ? (0, _stringUtils.escapeForAttributeSelector)(options.name, !!options.exact) : String(options.name)]);
if (options.pressed !== undefined) props.push(['pressed', String(options.pressed)]);
return `internal:role=${role}${props.map(([n, v]) => `[${n}=${v}]`).join('')}`;
}

View File

@@ -0,0 +1,384 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "InvalidSelectorError", {
enumerable: true,
get: function () {
return _cssParser.InvalidSelectorError;
}
});
exports.allEngineNames = allEngineNames;
exports.customCSSNames = void 0;
Object.defineProperty(exports, "isInvalidSelectorError", {
enumerable: true,
get: function () {
return _cssParser.isInvalidSelectorError;
}
});
exports.parseAttributeSelector = parseAttributeSelector;
exports.parseSelector = parseSelector;
exports.splitSelectorByFrame = splitSelectorByFrame;
exports.stringifySelector = stringifySelector;
var _cssParser = require("./cssParser");
/**
* Copyright (c) Microsoft Corporation.
*
* 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.
*/
const kNestedSelectorNames = new Set(['internal:has', 'internal:has-not', 'internal:and', 'internal:or', 'left-of', 'right-of', 'above', 'below', 'near']);
const kNestedSelectorNamesWithDistance = new Set(['left-of', 'right-of', 'above', 'below', 'near']);
const customCSSNames = new Set(['not', 'is', 'where', 'has', 'scope', 'light', 'visible', 'text', 'text-matches', 'text-is', 'has-text', 'above', 'below', 'right-of', 'left-of', 'near', 'nth-match']);
exports.customCSSNames = customCSSNames;
function parseSelector(selector) {
const result = parseSelectorString(selector);
const parts = result.parts.map(part => {
if (part.name === 'css' || part.name === 'css:light') {
if (part.name === 'css:light') part.body = ':light(' + part.body + ')';
const parsedCSS = (0, _cssParser.parseCSS)(part.body, customCSSNames);
return {
name: 'css',
body: parsedCSS.selector,
source: part.body
};
}
if (kNestedSelectorNames.has(part.name)) {
let innerSelector;
let distance;
try {
const unescaped = JSON.parse('[' + part.body + ']');
if (!Array.isArray(unescaped) || unescaped.length < 1 || unescaped.length > 2 || typeof unescaped[0] !== 'string') throw new _cssParser.InvalidSelectorError(`Malformed selector: ${part.name}=` + part.body);
innerSelector = unescaped[0];
if (unescaped.length === 2) {
if (typeof unescaped[1] !== 'number' || !kNestedSelectorNamesWithDistance.has(part.name)) throw new _cssParser.InvalidSelectorError(`Malformed selector: ${part.name}=` + part.body);
distance = unescaped[1];
}
} catch (e) {
throw new _cssParser.InvalidSelectorError(`Malformed selector: ${part.name}=` + part.body);
}
const result = {
name: part.name,
source: part.body,
body: {
parsed: parseSelector(innerSelector),
distance
}
};
if (result.body.parsed.parts.some(part => part.name === 'internal:control' && part.body === 'enter-frame')) throw new _cssParser.InvalidSelectorError(`Frames are not allowed inside "${part.name}" selectors`);
return result;
}
return {
...part,
source: part.body
};
});
if (kNestedSelectorNames.has(parts[0].name)) throw new _cssParser.InvalidSelectorError(`"${parts[0].name}" selector cannot be first`);
return {
capture: result.capture,
parts
};
}
function splitSelectorByFrame(selectorText) {
const selector = parseSelector(selectorText);
const result = [];
let chunk = {
parts: []
};
let chunkStartIndex = 0;
for (let i = 0; i < selector.parts.length; ++i) {
const part = selector.parts[i];
if (part.name === 'internal:control' && part.body === 'enter-frame') {
if (!chunk.parts.length) throw new _cssParser.InvalidSelectorError('Selector cannot start with entering frame, select the iframe first');
result.push(chunk);
chunk = {
parts: []
};
chunkStartIndex = i + 1;
continue;
}
if (selector.capture === i) chunk.capture = i - chunkStartIndex;
chunk.parts.push(part);
}
if (!chunk.parts.length) throw new _cssParser.InvalidSelectorError(`Selector cannot end with entering frame, while parsing selector ${selectorText}`);
result.push(chunk);
if (typeof selector.capture === 'number' && typeof result[result.length - 1].capture !== 'number') throw new _cssParser.InvalidSelectorError(`Can not capture the selector before diving into the frame. Only use * after the last frame has been selected`);
return result;
}
function stringifySelector(selector) {
if (typeof selector === 'string') return selector;
return selector.parts.map((p, i) => {
const prefix = p.name === 'css' ? '' : p.name + '=';
return `${i === selector.capture ? '*' : ''}${prefix}${p.source}`;
}).join(' >> ');
}
function allEngineNames(selector) {
const result = new Set();
const visit = selector => {
for (const part of selector.parts) {
result.add(part.name);
if (kNestedSelectorNames.has(part.name)) visit(part.body.parsed);
}
};
visit(selector);
return result;
}
function parseSelectorString(selector) {
let index = 0;
let quote;
let start = 0;
const result = {
parts: []
};
const append = () => {
const part = selector.substring(start, index).trim();
const eqIndex = part.indexOf('=');
let name;
let body;
if (eqIndex !== -1 && part.substring(0, eqIndex).trim().match(/^[a-zA-Z_0-9-+:*]+$/)) {
name = part.substring(0, eqIndex).trim();
body = part.substring(eqIndex + 1);
} else if (part.length > 1 && part[0] === '"' && part[part.length - 1] === '"') {
name = 'text';
body = part;
} else if (part.length > 1 && part[0] === "'" && part[part.length - 1] === "'") {
name = 'text';
body = part;
} else if (/^\(*\/\//.test(part) || part.startsWith('..')) {
// If selector starts with '//' or '//' prefixed with multiple opening
// parenthesis, consider xpath. @see https://github.com/microsoft/playwright/issues/817
// If selector starts with '..', consider xpath as well.
name = 'xpath';
body = part;
} else {
name = 'css';
body = part;
}
let capture = false;
if (name[0] === '*') {
capture = true;
name = name.substring(1);
}
result.parts.push({
name,
body
});
if (capture) {
if (result.capture !== undefined) throw new _cssParser.InvalidSelectorError(`Only one of the selectors can capture using * modifier`);
result.capture = result.parts.length - 1;
}
};
if (!selector.includes('>>')) {
index = selector.length;
append();
return result;
}
const shouldIgnoreTextSelectorQuote = () => {
const prefix = selector.substring(start, index);
const match = prefix.match(/^\s*text\s*=(.*)$/);
// Must be a text selector with some text before the quote.
return !!match && !!match[1];
};
while (index < selector.length) {
const c = selector[index];
if (c === '\\' && index + 1 < selector.length) {
index += 2;
} else if (c === quote) {
quote = undefined;
index++;
} else if (!quote && (c === '"' || c === '\'' || c === '`') && !shouldIgnoreTextSelectorQuote()) {
quote = c;
index++;
} else if (!quote && c === '>' && selector[index + 1] === '>') {
append();
index += 2;
start = index;
} else {
index++;
}
}
append();
return result;
}
function parseAttributeSelector(selector, allowUnquotedStrings) {
let wp = 0;
let EOL = selector.length === 0;
const next = () => selector[wp] || '';
const eat1 = () => {
const result = next();
++wp;
EOL = wp >= selector.length;
return result;
};
const syntaxError = stage => {
if (EOL) throw new _cssParser.InvalidSelectorError(`Unexpected end of selector while parsing selector \`${selector}\``);
throw new _cssParser.InvalidSelectorError(`Error while parsing selector \`${selector}\` - unexpected symbol "${next()}" at position ${wp}` + (stage ? ' during ' + stage : ''));
};
function skipSpaces() {
while (!EOL && /\s/.test(next())) eat1();
}
function isCSSNameChar(char) {
// https://www.w3.org/TR/css-syntax-3/#ident-token-diagram
return char >= '\u0080' // non-ascii
|| char >= '\u0030' && char <= '\u0039' // digit
|| char >= '\u0041' && char <= '\u005a' // uppercase letter
|| char >= '\u0061' && char <= '\u007a' // lowercase letter
|| char >= '\u0030' && char <= '\u0039' // digit
|| char === '\u005f' // "_"
|| char === '\u002d'; // "-"
}
function readIdentifier() {
let result = '';
skipSpaces();
while (!EOL && isCSSNameChar(next())) result += eat1();
return result;
}
function readQuotedString(quote) {
let result = eat1();
if (result !== quote) syntaxError('parsing quoted string');
while (!EOL && next() !== quote) {
if (next() === '\\') eat1();
result += eat1();
}
if (next() !== quote) syntaxError('parsing quoted string');
result += eat1();
return result;
}
function readRegularExpression() {
if (eat1() !== '/') syntaxError('parsing regular expression');
let source = '';
let inClass = false;
// https://262.ecma-international.org/11.0/#sec-literals-regular-expression-literals
while (!EOL) {
if (next() === '\\') {
source += eat1();
if (EOL) syntaxError('parsing regular expressiion');
} else if (inClass && next() === ']') {
inClass = false;
} else if (!inClass && next() === '[') {
inClass = true;
} else if (!inClass && next() === '/') {
break;
}
source += eat1();
}
if (eat1() !== '/') syntaxError('parsing regular expression');
let flags = '';
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
while (!EOL && next().match(/[dgimsuy]/)) flags += eat1();
try {
return new RegExp(source, flags);
} catch (e) {
throw new _cssParser.InvalidSelectorError(`Error while parsing selector \`${selector}\`: ${e.message}`);
}
}
function readAttributeToken() {
let token = '';
skipSpaces();
if (next() === `'` || next() === `"`) token = readQuotedString(next()).slice(1, -1);else token = readIdentifier();
if (!token) syntaxError('parsing property path');
return token;
}
function readOperator() {
skipSpaces();
let op = '';
if (!EOL) op += eat1();
if (!EOL && op !== '=') op += eat1();
if (!['=', '*=', '^=', '$=', '|=', '~='].includes(op)) syntaxError('parsing operator');
return op;
}
function readAttribute() {
// skip leading [
eat1();
// read attribute name:
// foo.bar
// 'foo' . "ba zz"
const jsonPath = [];
jsonPath.push(readAttributeToken());
skipSpaces();
while (next() === '.') {
eat1();
jsonPath.push(readAttributeToken());
skipSpaces();
}
// check property is truthy: [enabled]
if (next() === ']') {
eat1();
return {
name: jsonPath.join('.'),
jsonPath,
op: '<truthy>',
value: null,
caseSensitive: false
};
}
const operator = readOperator();
let value = undefined;
let caseSensitive = true;
skipSpaces();
if (next() === '/') {
if (operator !== '=') throw new _cssParser.InvalidSelectorError(`Error while parsing selector \`${selector}\` - cannot use ${operator} in attribute with regular expression`);
value = readRegularExpression();
} else if (next() === `'` || next() === `"`) {
value = readQuotedString(next()).slice(1, -1);
skipSpaces();
if (next() === 'i' || next() === 'I') {
caseSensitive = false;
eat1();
} else if (next() === 's' || next() === 'S') {
caseSensitive = true;
eat1();
}
} else {
value = '';
while (!EOL && (isCSSNameChar(next()) || next() === '+' || next() === '.')) value += eat1();
if (value === 'true') {
value = true;
} else if (value === 'false') {
value = false;
} else {
if (!allowUnquotedStrings) {
value = +value;
if (Number.isNaN(value)) syntaxError('parsing attribute value');
}
}
}
skipSpaces();
if (next() !== ']') syntaxError('parsing attribute value');
eat1();
if (operator !== '=' && typeof value !== 'string') throw new _cssParser.InvalidSelectorError(`Error while parsing selector \`${selector}\` - cannot use ${operator} in attribute with non-string matching value - ${value}`);
return {
name: jsonPath.join('.'),
jsonPath,
op: operator,
value,
caseSensitive
};
}
const result = {
name: '',
attributes: []
};
result.name = readIdentifier();
skipSpaces();
while (next() === '[') {
result.attributes.push(readAttribute());
skipSpaces();
}
if (!EOL) syntaxError(undefined);
if (!result.name && !result.attributes.length) throw new _cssParser.InvalidSelectorError(`Error while parsing selector \`${selector}\` - selector cannot be empty`);
return result;
}

View File

@@ -0,0 +1,76 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.cssEscape = cssEscape;
exports.escapeForAttributeSelector = escapeForAttributeSelector;
exports.escapeForTextSelector = escapeForTextSelector;
exports.escapeWithQuotes = escapeWithQuotes;
exports.isString = isString;
exports.normalizeWhiteSpace = normalizeWhiteSpace;
exports.toSnakeCase = toSnakeCase;
exports.toTitleCase = toTitleCase;
/**
* Copyright (c) Microsoft Corporation.
*
* 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.
*/
// NOTE: this function should not be used to escape any selectors.
function escapeWithQuotes(text, char = '\'') {
const stringified = JSON.stringify(text);
const escapedText = stringified.substring(1, stringified.length - 1).replace(/\\"/g, '"');
if (char === '\'') return char + escapedText.replace(/[']/g, '\\\'') + char;
if (char === '"') return char + escapedText.replace(/["]/g, '\\"') + char;
if (char === '`') return char + escapedText.replace(/[`]/g, '`') + char;
throw new Error('Invalid escape char');
}
function isString(obj) {
return typeof obj === 'string' || obj instanceof String;
}
function toTitleCase(name) {
return name.charAt(0).toUpperCase() + name.substring(1);
}
function toSnakeCase(name) {
// E.g. ignoreHTTPSErrors => ignore_https_errors.
return name.replace(/([a-z0-9])([A-Z])/g, '$1_$2').replace(/([A-Z])([A-Z][a-z])/g, '$1_$2').toLowerCase();
}
function cssEscape(s) {
let result = '';
for (let i = 0; i < s.length; i++) result += cssEscapeOne(s, i);
return result;
}
function cssEscapeOne(s, i) {
// https://drafts.csswg.org/cssom/#serialize-an-identifier
const c = s.charCodeAt(i);
if (c === 0x0000) return '\uFFFD';
if (c >= 0x0001 && c <= 0x001f || c >= 0x0030 && c <= 0x0039 && (i === 0 || i === 1 && s.charCodeAt(0) === 0x002d)) return '\\' + c.toString(16) + ' ';
if (i === 0 && c === 0x002d && s.length === 1) return '\\' + s.charAt(i);
if (c >= 0x0080 || c === 0x002d || c === 0x005f || c >= 0x0030 && c <= 0x0039 || c >= 0x0041 && c <= 0x005a || c >= 0x0061 && c <= 0x007a) return s.charAt(i);
return '\\' + s.charAt(i);
}
function normalizeWhiteSpace(text) {
return text.replace(/\u200b/g, '').trim().replace(/\s+/g, ' ');
}
function escapeForTextSelector(text, exact) {
if (typeof text !== 'string') return String(text);
return `${JSON.stringify(text)}${exact ? 's' : 'i'}`;
}
function escapeForAttributeSelector(value, exact) {
// TODO: this should actually be
// cssEscape(value).replace(/\\ /g, ' ')
// However, our attribute selectors do not conform to CSS parsing spec,
// so we escape them differently.
return `"${value.replace(/\\/g, '\\\\').replace(/["]/g, '\\"')}"${exact ? 's' : 'i'}`;
}

View File

@@ -0,0 +1,39 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.parseClientSideCallMetadata = parseClientSideCallMetadata;
/**
* Copyright (c) Microsoft Corporation.
*
* 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.
*/
function parseClientSideCallMetadata(data) {
const result = new Map();
const {
files,
stacks
} = data;
for (const s of stacks) {
const [id, ff] = s;
result.set(`call@${id}`, ff.map(f => ({
file: files[f[0]],
line: f[1],
column: f[2],
function: f[3]
})));
}
return result;
}

View File

@@ -0,0 +1,78 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.getLinuxDistributionInfo = getLinuxDistributionInfo;
exports.getLinuxDistributionInfoSync = getLinuxDistributionInfoSync;
var _fs = _interopRequireDefault(require("fs"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* Copyright 2017 Google Inc. All rights reserved.
* Modifications copyright (c) Microsoft Corporation.
*
* 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.
*/
let didFailToReadOSRelease = false;
let osRelease;
async function getLinuxDistributionInfo() {
if (process.platform !== 'linux') return undefined;
if (!osRelease && !didFailToReadOSRelease) {
try {
var _fields$get, _fields$get2;
// List of /etc/os-release values for different distributions could be
// found here: https://gist.github.com/aslushnikov/8ceddb8288e4cf9db3039c02e0f4fb75
const osReleaseText = await _fs.default.promises.readFile('/etc/os-release', 'utf8');
const fields = parseOSReleaseText(osReleaseText);
osRelease = {
id: (_fields$get = fields.get('id')) !== null && _fields$get !== void 0 ? _fields$get : '',
version: (_fields$get2 = fields.get('version_id')) !== null && _fields$get2 !== void 0 ? _fields$get2 : ''
};
} catch (e) {
didFailToReadOSRelease = true;
}
}
return osRelease;
}
function getLinuxDistributionInfoSync() {
if (process.platform !== 'linux') return undefined;
if (!osRelease && !didFailToReadOSRelease) {
try {
var _fields$get3, _fields$get4;
// List of /etc/os-release values for different distributions could be
// found here: https://gist.github.com/aslushnikov/8ceddb8288e4cf9db3039c02e0f4fb75
const osReleaseText = _fs.default.readFileSync('/etc/os-release', 'utf8');
const fields = parseOSReleaseText(osReleaseText);
osRelease = {
id: (_fields$get3 = fields.get('id')) !== null && _fields$get3 !== void 0 ? _fields$get3 : '',
version: (_fields$get4 = fields.get('version_id')) !== null && _fields$get4 !== void 0 ? _fields$get4 : ''
};
} catch (e) {
didFailToReadOSRelease = true;
}
}
return osRelease;
}
function parseOSReleaseText(osReleaseText) {
const fields = new Map();
for (const line of osReleaseText.split('\n')) {
const tokens = line.split('=');
const name = tokens.shift();
let value = tokens.join('=').trim();
if (value.startsWith('"') && value.endsWith('"')) value = value.substring(1, value.length - 1);
if (!name) continue;
fields.set(name.toLowerCase(), value);
}
return fields;
}

View File

@@ -0,0 +1,90 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.ScopedRace = exports.ManualPromise = void 0;
var _stackTrace = require("./stackTrace");
let _Symbol$species, _Symbol$toStringTag;
/**
* Copyright (c) Microsoft Corporation.
*
* 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.
*/
_Symbol$species = Symbol.species;
_Symbol$toStringTag = Symbol.toStringTag;
class ManualPromise extends Promise {
constructor() {
let resolve;
let reject;
super((f, r) => {
resolve = f;
reject = r;
});
this._resolve = void 0;
this._reject = void 0;
this._isDone = void 0;
this._isDone = false;
this._resolve = resolve;
this._reject = reject;
}
isDone() {
return this._isDone;
}
resolve(t) {
this._isDone = true;
this._resolve(t);
}
reject(e) {
this._isDone = true;
this._reject(e);
}
static get [_Symbol$species]() {
return Promise;
}
get [_Symbol$toStringTag]() {
return 'ManualPromise';
}
}
exports.ManualPromise = ManualPromise;
class ScopedRace {
constructor() {
this._terminateError = void 0;
this._terminatePromises = new Map();
}
scopeClosed(error) {
this._terminateError = error;
for (const [p, e] of this._terminatePromises) {
(0, _stackTrace.rewriteErrorMessage)(e, error.message);
p.resolve(e);
}
}
async race(promise) {
return this._race([promise], false);
}
async safeRace(promise, defaultValue) {
return this._race([promise], true, defaultValue);
}
async _race(promises, safe, defaultValue) {
const terminatePromise = new ManualPromise();
if (this._terminateError) terminatePromise.resolve(this._terminateError);
const error = new Error('');
this._terminatePromises.set(terminatePromise, error);
try {
return await Promise.race([terminatePromise.then(e => safe ? defaultValue : Promise.reject(e)), ...promises]);
} finally {
this._terminatePromises.delete(terminatePromise);
}
}
}
exports.ScopedRace = ScopedRace;

View File

@@ -0,0 +1,29 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.isJsonMimeType = isJsonMimeType;
exports.isTextualMimeType = isTextualMimeType;
/**
* Copyright (c) Microsoft Corporation.
*
* 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.
*/
function isJsonMimeType(mimeType) {
return !!mimeType.match(/^(application\/json|application\/.*?\+json|text\/(x-)?json)(;\s*charset=.*)?$/);
}
function isTextualMimeType(mimeType) {
return !!mimeType.match(/^(text\/.*?|application\/(json|(x-)?javascript|xml.*?|ecmascript|graphql|x-www-form-urlencoded)|image\/svg(\+xml)?|application\/.*?(\+json|\+xml))(;\s*charset=.*)?$/);
}

View File

@@ -0,0 +1,75 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.MultiMap = void 0;
let _Symbol$iterator;
_Symbol$iterator = Symbol.iterator;
/**
* Copyright (c) Microsoft Corporation.
*
* 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.
*/
class MultiMap {
constructor() {
this._map = void 0;
this._map = new Map();
}
set(key, value) {
let values = this._map.get(key);
if (!values) {
values = [];
this._map.set(key, values);
}
values.push(value);
}
get(key) {
return this._map.get(key) || [];
}
has(key) {
return this._map.has(key);
}
delete(key, value) {
const values = this._map.get(key);
if (!values) return;
if (values.includes(value)) this._map.set(key, values.filter(v => value !== v));
}
deleteAll(key) {
this._map.delete(key);
}
hasValue(key, value) {
const values = this._map.get(key);
if (!values) return false;
return values.includes(value);
}
get size() {
return this._map.size;
}
[_Symbol$iterator]() {
return this._map[Symbol.iterator]();
}
keys() {
return this._map.keys();
}
values() {
const result = [];
for (const key of this.keys()) result.push(...this.get(key));
return result;
}
clear() {
this._map.clear();
}
}
exports.MultiMap = MultiMap;

View File

@@ -0,0 +1,159 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.NET_DEFAULT_TIMEOUT = void 0;
exports.constructURLBasedOnBaseURL = constructURLBasedOnBaseURL;
exports.createHttpServer = createHttpServer;
exports.createHttpsServer = createHttpsServer;
exports.fetchData = fetchData;
exports.httpRequest = httpRequest;
exports.urlMatches = urlMatches;
exports.urlMatchesEqual = urlMatchesEqual;
var _http = _interopRequireDefault(require("http"));
var _https = _interopRequireDefault(require("https"));
var _utilsBundle = require("../utilsBundle");
var URL = _interopRequireWildcard(require("url"));
var _rtti = require("./rtti");
var _glob = require("./glob");
var _happyEyeballs = require("./happy-eyeballs");
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* Copyright (c) Microsoft Corporation.
*
* 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.
*/
const NET_DEFAULT_TIMEOUT = 30_000;
exports.NET_DEFAULT_TIMEOUT = NET_DEFAULT_TIMEOUT;
function httpRequest(params, onResponse, onError) {
var _params$timeout;
const parsedUrl = URL.parse(params.url);
let options = {
...parsedUrl,
agent: parsedUrl.protocol === 'https:' ? _happyEyeballs.httpsHappyEyeballsAgent : _happyEyeballs.httpHappyEyeballsAgent,
method: params.method || 'GET',
headers: params.headers
};
if (params.rejectUnauthorized !== undefined) options.rejectUnauthorized = params.rejectUnauthorized;
const timeout = (_params$timeout = params.timeout) !== null && _params$timeout !== void 0 ? _params$timeout : NET_DEFAULT_TIMEOUT;
const proxyURL = (0, _utilsBundle.getProxyForUrl)(params.url);
if (proxyURL) {
const parsedProxyURL = URL.parse(proxyURL);
if (params.url.startsWith('http:')) {
options = {
path: parsedUrl.href,
host: parsedProxyURL.hostname,
port: parsedProxyURL.port,
headers: options.headers,
method: options.method
};
} else {
parsedProxyURL.secureProxy = parsedProxyURL.protocol === 'https:';
options.agent = new _utilsBundle.HttpsProxyAgent(parsedProxyURL);
options.rejectUnauthorized = false;
}
}
const requestCallback = res => {
const statusCode = res.statusCode || 0;
if (statusCode >= 300 && statusCode < 400 && res.headers.location) httpRequest({
...params,
url: new URL.URL(res.headers.location, params.url).toString()
}, onResponse, onError);else onResponse(res);
};
const request = options.protocol === 'https:' ? _https.default.request(options, requestCallback) : _http.default.request(options, requestCallback);
request.on('error', onError);
if (timeout !== undefined) {
const rejectOnTimeout = () => {
onError(new Error(`Request to ${params.url} timed out after ${timeout}ms`));
request.abort();
};
if (timeout <= 0) {
rejectOnTimeout();
return;
}
request.setTimeout(timeout, rejectOnTimeout);
}
request.end(params.data);
}
function fetchData(params, onError) {
return new Promise((resolve, reject) => {
httpRequest(params, async response => {
if (response.statusCode !== 200) {
const error = onError ? await onError(params, response) : new Error(`fetch failed: server returned code ${response.statusCode}. URL: ${params.url}`);
reject(error);
return;
}
let body = '';
response.on('data', chunk => body += chunk);
response.on('error', error => reject(error));
response.on('end', () => resolve(body));
}, reject);
});
}
function urlMatchesEqual(match1, match2) {
if ((0, _rtti.isRegExp)(match1) && (0, _rtti.isRegExp)(match2)) return match1.source === match2.source && match1.flags === match2.flags;
return match1 === match2;
}
function urlMatches(baseURL, urlString, match) {
if (match === undefined || match === '') return true;
if ((0, _rtti.isString)(match) && !match.startsWith('*')) match = constructURLBasedOnBaseURL(baseURL, match);
if ((0, _rtti.isString)(match)) match = (0, _glob.globToRegex)(match);
if ((0, _rtti.isRegExp)(match)) return match.test(urlString);
if (typeof match === 'string' && match === urlString) return true;
const url = parsedURL(urlString);
if (!url) return false;
if (typeof match === 'string') return url.pathname === match;
if (typeof match !== 'function') throw new Error('url parameter should be string, RegExp or function');
return match(url);
}
function parsedURL(url) {
try {
return new URL.URL(url);
} catch (e) {
return null;
}
}
function constructURLBasedOnBaseURL(baseURL, givenURL) {
try {
return new URL.URL(givenURL, baseURL).toString();
} catch (e) {
return givenURL;
}
}
function createHttpServer(...args) {
const server = _http.default.createServer(...args);
decorateServer(server);
return server;
}
function createHttpsServer(...args) {
const server = _https.default.createServer(...args);
decorateServer(server);
return server;
}
function decorateServer(server) {
const sockets = new Set();
server.on('connection', socket => {
sockets.add(socket);
socket.once('close', () => sockets.delete(socket));
});
const close = server.close;
server.close = callback => {
for (const socket of sockets) socket.destroy();
sockets.clear();
return close.call(server, callback);
};
}

View File

@@ -0,0 +1,219 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.envArrayToObject = envArrayToObject;
exports.gracefullyCloseAll = gracefullyCloseAll;
exports.gracefullyCloseSet = void 0;
exports.launchProcess = launchProcess;
var childProcess = _interopRequireWildcard(require("child_process"));
var readline = _interopRequireWildcard(require("readline"));
var path = _interopRequireWildcard(require("path"));
var _ = require("./");
var _fileUtils = require("./fileUtils");
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
/**
* Copyright 2017 Google Inc. All rights reserved.
* Modifications copyright (c) Microsoft Corporation.
*
* 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.
*/
const gracefullyCloseSet = new Set();
exports.gracefullyCloseSet = gracefullyCloseSet;
const killSet = new Set();
async function gracefullyCloseAll() {
await Promise.all(Array.from(gracefullyCloseSet).map(gracefullyClose => gracefullyClose().catch(e => {})));
}
function exitHandler() {
for (const kill of killSet) kill();
}
let sigintHandlerCalled = false;
function sigintHandler() {
const exitWithCode130 = () => {
// Give tests a chance to see that launched process did exit and dispatch any async calls.
if ((0, _.isUnderTest)()) setTimeout(() => process.exit(130), 1000);else process.exit(130);
};
if (sigintHandlerCalled) {
// Resort to default handler from this point on, just in case we hang/stall.
process.off('SIGINT', sigintHandler);
// Upon second Ctrl+C, immediately kill browsers and exit.
// This prevents hanging in the case where closing the browser takes a lot of time or is buggy.
for (const kill of killSet) kill();
exitWithCode130();
} else {
sigintHandlerCalled = true;
gracefullyCloseAll().then(() => exitWithCode130());
}
}
function sigtermHandler() {
gracefullyCloseAll();
}
function sighupHandler() {
gracefullyCloseAll();
}
const installedHandlers = new Set();
const processHandlers = {
exit: exitHandler,
SIGINT: sigintHandler,
SIGTERM: sigtermHandler,
SIGHUP: sighupHandler
};
function addProcessHandlerIfNeeded(name) {
if (!installedHandlers.has(name)) {
installedHandlers.add(name);
process.on(name, processHandlers[name]);
}
}
async function launchProcess(options) {
const stdio = options.stdio === 'pipe' ? ['ignore', 'pipe', 'pipe', 'pipe', 'pipe'] : ['pipe', 'pipe', 'pipe'];
options.log(`<launching> ${options.command} ${options.args ? options.args.join(' ') : ''}`);
const spawnOptions = {
// On non-windows platforms, `detached: true` makes child process a leader of a new
// process group, making it possible to kill child process tree with `.kill(-pid)` command.
// @see https://nodejs.org/api/child_process.html#child_process_options_detached
detached: process.platform !== 'win32',
env: options.env,
cwd: options.cwd,
shell: options.shell,
stdio
};
const spawnedProcess = childProcess.spawn(options.command, options.args || [], spawnOptions);
const cleanup = async () => {
options.log(`[pid=${spawnedProcess.pid || 'N/A'}] starting temporary directories cleanup`);
const errors = await (0, _fileUtils.removeFolders)(options.tempDirectories);
for (let i = 0; i < options.tempDirectories.length; ++i) {
if (errors[i]) options.log(`[pid=${spawnedProcess.pid || 'N/A'}] exception while removing ${options.tempDirectories[i]}: ${errors[i]}`);
}
options.log(`[pid=${spawnedProcess.pid || 'N/A'}] finished temporary directories cleanup`);
};
// Prevent Unhandled 'error' event.
spawnedProcess.on('error', () => {});
if (!spawnedProcess.pid) {
let failed;
const failedPromise = new Promise((f, r) => failed = f);
spawnedProcess.once('error', error => {
failed(new Error('Failed to launch: ' + error));
});
return cleanup().then(() => failedPromise).then(e => Promise.reject(e));
}
options.log(`<launched> pid=${spawnedProcess.pid}`);
const stdout = readline.createInterface({
input: spawnedProcess.stdout
});
stdout.on('line', data => {
options.log(`[pid=${spawnedProcess.pid}][out] ` + data);
});
const stderr = readline.createInterface({
input: spawnedProcess.stderr
});
stderr.on('line', data => {
options.log(`[pid=${spawnedProcess.pid}][err] ` + data);
});
let processClosed = false;
let fulfillCleanup = () => {};
const waitForCleanup = new Promise(f => fulfillCleanup = f);
spawnedProcess.once('exit', (exitCode, signal) => {
options.log(`[pid=${spawnedProcess.pid}] <process did exit: exitCode=${exitCode}, signal=${signal}>`);
processClosed = true;
gracefullyCloseSet.delete(gracefullyClose);
killSet.delete(killProcessAndCleanup);
options.onExit(exitCode, signal);
// Cleanup as process exits.
cleanup().then(fulfillCleanup);
});
addProcessHandlerIfNeeded('exit');
if (options.handleSIGINT) addProcessHandlerIfNeeded('SIGINT');
if (options.handleSIGTERM) addProcessHandlerIfNeeded('SIGTERM');
if (options.handleSIGHUP) addProcessHandlerIfNeeded('SIGHUP');
gracefullyCloseSet.add(gracefullyClose);
killSet.add(killProcessAndCleanup);
let gracefullyClosing = false;
async function gracefullyClose() {
// We keep listeners until we are done, to handle 'exit' and 'SIGINT' while
// asynchronously closing to prevent zombie processes. This might introduce
// reentrancy to this function, for example user sends SIGINT second time.
// In this case, let's forcefully kill the process.
if (gracefullyClosing) {
options.log(`[pid=${spawnedProcess.pid}] <forecefully close>`);
killProcess();
await waitForCleanup; // Ensure the process is dead and we have cleaned up.
return;
}
gracefullyClosing = true;
options.log(`[pid=${spawnedProcess.pid}] <gracefully close start>`);
await options.attemptToGracefullyClose().catch(() => killProcess());
await waitForCleanup; // Ensure the process is dead and we have cleaned up.
options.log(`[pid=${spawnedProcess.pid}] <gracefully close end>`);
}
// This method has to be sync to be used in the 'exit' event handler.
function killProcess() {
gracefullyCloseSet.delete(gracefullyClose);
killSet.delete(killProcessAndCleanup);
options.log(`[pid=${spawnedProcess.pid}] <kill>`);
if (spawnedProcess.pid && !spawnedProcess.killed && !processClosed) {
options.log(`[pid=${spawnedProcess.pid}] <will force kill>`);
// Force kill the browser.
try {
if (process.platform === 'win32') {
const taskkillProcess = childProcess.spawnSync(`taskkill /pid ${spawnedProcess.pid} /T /F`, {
shell: true
});
const [stdout, stderr] = [taskkillProcess.stdout.toString(), taskkillProcess.stderr.toString()];
if (stdout) options.log(`[pid=${spawnedProcess.pid}] taskkill stdout: ${stdout}`);
if (stderr) options.log(`[pid=${spawnedProcess.pid}] taskkill stderr: ${stderr}`);
} else {
process.kill(-spawnedProcess.pid, 'SIGKILL');
}
} catch (e) {
options.log(`[pid=${spawnedProcess.pid}] exception while trying to kill process: ${e}`);
// the process might have already stopped
}
} else {
options.log(`[pid=${spawnedProcess.pid}] <skipped force kill spawnedProcess.killed=${spawnedProcess.killed} processClosed=${processClosed}>`);
}
}
function killProcessAndCleanup() {
killProcess();
options.log(`[pid=${spawnedProcess.pid || 'N/A'}] starting temporary directories cleanup`);
if (options.tempDirectories.length) {
const cleanupProcess = childProcess.spawnSync(process.argv0, [path.join(__dirname, 'processLauncherCleanupEntrypoint.js'), ...options.tempDirectories]);
const [stdout, stderr] = [cleanupProcess.stdout.toString(), cleanupProcess.stderr.toString()];
if (stdout) options.log(`[pid=${spawnedProcess.pid || 'N/A'}] ${stdout}`);
if (stderr) options.log(`[pid=${spawnedProcess.pid || 'N/A'}] ${stderr}`);
}
options.log(`[pid=${spawnedProcess.pid || 'N/A'}] finished temporary directories cleanup`);
}
function killAndWait() {
killProcess();
return waitForCleanup;
}
return {
launchedProcess: spawnedProcess,
gracefullyClose,
kill: killAndWait
};
}
function envArrayToObject(env) {
const result = {};
for (const {
name,
value
} of env) result[name] = value;
return result;
}

View File

@@ -0,0 +1,29 @@
"use strict";
var _fileUtils = require("./fileUtils");
/**
* Copyright (c) Microsoft Corporation.
*
* 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.
*/
(async () => {
const dirs = process.argv.slice(2);
const errors = await (0, _fileUtils.removeFolders)(dirs);
for (let i = 0; i < dirs.length; ++i) {
if (errors[i]) {
// eslint-disable-next-line no-console
console.error(`exception while removing ${dirs[i]}: ${errors[i]}`);
}
}
})();

View File

@@ -0,0 +1,53 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.startProfiling = startProfiling;
exports.stopProfiling = stopProfiling;
var fs = _interopRequireWildcard(require("fs"));
var path = _interopRequireWildcard(require("path"));
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
/**
* Copyright Microsoft Corporation. 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.
*/
const profileDir = process.env.PWTEST_PROFILE_DIR || '';
let session;
async function startProfiling() {
if (!profileDir) return;
session = new (require('inspector').Session)();
session.connect();
await new Promise(f => {
session.post('Profiler.enable', () => {
session.post('Profiler.start', f);
});
});
}
async function stopProfiling(profileName) {
if (!profileDir) return;
await new Promise(f => session.post('Profiler.stop', (err, {
profile
}) => {
if (!err) {
fs.mkdirSync(profileDir, {
recursive: true
});
fs.writeFileSync(path.join(profileDir, profileName + '.json'), JSON.stringify(profile));
}
f();
}));
}

View File

@@ -0,0 +1,41 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.isError = isError;
exports.isLikelyNpxGlobal = void 0;
exports.isObject = isObject;
exports.isRegExp = isRegExp;
exports.isString = isString;
/**
* Copyright (c) Microsoft Corporation.
*
* 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.
*/
function isString(obj) {
return typeof obj === 'string' || obj instanceof String;
}
function isRegExp(obj) {
return obj instanceof RegExp || Object.prototype.toString.call(obj) === '[object RegExp]';
}
function isObject(obj) {
return typeof obj === 'object' && obj !== null;
}
function isError(obj) {
var _Object$getPrototypeO;
return obj instanceof Error || obj && ((_Object$getPrototypeO = Object.getPrototypeOf(obj)) === null || _Object$getPrototypeO === void 0 ? void 0 : _Object$getPrototypeO.name) === 'Error';
}
const isLikelyNpxGlobal = () => process.argv.length >= 2 && process.argv[1].includes('_npx');
exports.isLikelyNpxGlobal = isLikelyNpxGlobal;

View File

@@ -0,0 +1,45 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.spawnAsync = spawnAsync;
var _child_process = require("child_process");
/**
* Copyright (c) Microsoft Corporation.
*
* 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.
*/
function spawnAsync(cmd, args, options = {}) {
const process = (0, _child_process.spawn)(cmd, args, Object.assign({
windowsHide: true
}, options));
return new Promise(resolve => {
let stdout = '';
let stderr = '';
if (process.stdout) process.stdout.on('data', data => stdout += data);
if (process.stderr) process.stderr.on('data', data => stderr += data);
process.on('close', code => resolve({
stdout,
stderr,
code
}));
process.on('error', error => resolve({
stdout,
stderr,
code: 0,
error
}));
});
}

View File

@@ -0,0 +1,105 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.addInternalStackPrefix = void 0;
exports.captureLibraryStackTrace = captureLibraryStackTrace;
exports.captureRawStack = captureRawStack;
exports.rewriteErrorMessage = rewriteErrorMessage;
exports.splitErrorMessage = splitErrorMessage;
var _path = _interopRequireDefault(require("path"));
var _utilsBundle = require("../utilsBundle");
var _ = require("./");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* Copyright (c) Microsoft Corporation.
*
* 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.
*/
function rewriteErrorMessage(e, newMessage) {
var _e$stack;
const lines = (((_e$stack = e.stack) === null || _e$stack === void 0 ? void 0 : _e$stack.split('\n')) || []).filter(l => l.startsWith(' at '));
e.message = newMessage;
const errorTitle = `${e.name}: ${e.message}`;
if (lines.length) e.stack = `${errorTitle}\n${lines.join('\n')}`;
return e;
}
const CORE_DIR = _path.default.resolve(__dirname, '..', '..');
const COVERAGE_PATH = _path.default.join(CORE_DIR, '..', '..', 'tests', 'config', 'coverage.js');
const internalStackPrefixes = [CORE_DIR];
const addInternalStackPrefix = prefix => internalStackPrefixes.push(prefix);
exports.addInternalStackPrefix = addInternalStackPrefix;
function captureRawStack() {
const stackTraceLimit = Error.stackTraceLimit;
Error.stackTraceLimit = 50;
const error = new Error();
const stack = error.stack || '';
Error.stackTraceLimit = stackTraceLimit;
return stack.split('\n');
}
function captureLibraryStackTrace(rawStack) {
const stack = rawStack || captureRawStack();
const isTesting = (0, _.isUnderTest)();
let parsedFrames = stack.map(line => {
const frame = (0, _utilsBundle.parseStackTraceLine)(line);
if (!frame || !frame.file) return null;
if (!process.env.PWDEBUGIMPL && isTesting && frame.file.includes(COVERAGE_PATH)) return null;
const isPlaywrightLibrary = frame.file.startsWith(CORE_DIR);
const parsed = {
frame,
frameText: line,
isPlaywrightLibrary
};
return parsed;
}).filter(Boolean);
let apiName = '';
const allFrames = parsedFrames;
// Deepest transition between non-client code calling into client
// code is the api entry.
for (let i = 0; i < parsedFrames.length - 1; i++) {
const parsedFrame = parsedFrames[i];
if (parsedFrame.isPlaywrightLibrary && !parsedFrames[i + 1].isPlaywrightLibrary) {
apiName = apiName || normalizeAPIName(parsedFrame.frame.function);
break;
}
}
function normalizeAPIName(name) {
if (!name) return '';
const match = name.match(/(API|JS|CDP|[A-Z])(.*)/);
if (!match) return name;
return match[1].toLowerCase() + match[2];
}
// This is for the inspector so that it did not include the test runner stack frames.
parsedFrames = parsedFrames.filter(f => {
if (process.env.PWDEBUGIMPL) return true;
if (internalStackPrefixes.some(prefix => f.frame.file.startsWith(prefix))) return false;
return true;
});
return {
allFrames: allFrames.map(p => p.frame),
frames: parsedFrames.map(p => p.frame),
frameTexts: parsedFrames.map(p => p.frameText),
apiName
};
}
function splitErrorMessage(message) {
const separationIdx = message.indexOf(':');
return {
name: separationIdx !== -1 ? message.slice(0, separationIdx) : '',
message: separationIdx !== -1 && separationIdx + 2 <= message.length ? message.substring(separationIdx + 2) : message
};
}

View File

@@ -0,0 +1,58 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.makeWaitForNextTask = makeWaitForNextTask;
/**
* Copyright (c) Microsoft Corporation.
*
* 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.
*/
// See https://joel.tools/microtasks/
function makeWaitForNextTask() {
// As of Mar 2021, Electron v12 doesn't create new task with `setImmediate` despite
// using Node 14 internally, so we fallback to `setTimeout(0)` instead.
// @see https://github.com/electron/electron/issues/28261
if (process.versions.electron) return callback => setTimeout(callback, 0);
if (parseInt(process.versions.node, 10) >= 11) return setImmediate;
// Unlike Node 11, Node 10 and less have a bug with Task and MicroTask execution order:
// - https://github.com/nodejs/node/issues/22257
//
// So we can't simply run setImmediate to dispatch code in a following task.
// However, we can run setImmediate from-inside setImmediate to make sure we're getting
// in the following task.
let spinning = false;
const callbacks = [];
const loop = () => {
const callback = callbacks.shift();
if (!callback) {
spinning = false;
return;
}
setImmediate(loop);
// Make sure to call callback() as the last thing since it's
// untrusted code that might throw.
callback();
};
return callback => {
callbacks.push(callback);
if (!spinning) {
spinning = true;
setImmediate(loop);
}
};
}

View File

@@ -0,0 +1,26 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.monotonicTime = monotonicTime;
/**
* Copyright (c) Microsoft Corporation.
*
* 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.
*/
function monotonicTime() {
const [seconds, nanoseconds] = process.hrtime();
return seconds * 1000 + (nanoseconds / 1000 | 0) / 1000;
}

View File

@@ -0,0 +1,128 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.TimeoutRunnerError = exports.TimeoutRunner = void 0;
exports.pollAgainstTimeout = pollAgainstTimeout;
exports.raceAgainstTimeout = raceAgainstTimeout;
var _manualPromise = require("./manualPromise");
var _ = require("./");
/**
* Copyright (c) Microsoft Corporation.
*
* 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.
*/
class TimeoutRunnerError extends Error {}
exports.TimeoutRunnerError = TimeoutRunnerError;
class TimeoutRunner {
constructor(timeout) {
this._running = void 0;
this._timeout = void 0;
this._elapsed = void 0;
this._timeout = timeout;
this._elapsed = 0;
}
async run(cb) {
const running = this._running = {
lastElapsedSync: (0, _.monotonicTime)(),
timer: undefined,
timeoutPromise: new _manualPromise.ManualPromise()
};
try {
const resultPromise = Promise.race([cb(), running.timeoutPromise]);
this._updateTimeout(running, this._timeout);
return await resultPromise;
} finally {
this._updateTimeout(running, 0);
if (this._running === running) this._running = undefined;
}
}
interrupt() {
if (this._running) this._updateTimeout(this._running, -1);
}
elapsed() {
this._syncElapsedAndStart();
return this._elapsed;
}
updateTimeout(timeout, elapsed) {
this._timeout = timeout;
if (elapsed !== undefined) {
this._syncElapsedAndStart();
this._elapsed = elapsed;
}
if (this._running) this._updateTimeout(this._running, timeout);
}
_syncElapsedAndStart() {
if (this._running) {
const now = (0, _.monotonicTime)();
this._elapsed += now - this._running.lastElapsedSync;
this._running.lastElapsedSync = now;
}
}
_updateTimeout(running, timeout) {
if (running.timer) {
clearTimeout(running.timer);
running.timer = undefined;
}
this._syncElapsedAndStart();
if (timeout === 0) return;
timeout = timeout - this._elapsed;
if (timeout <= 0) running.timeoutPromise.reject(new TimeoutRunnerError());else running.timer = setTimeout(() => running.timeoutPromise.reject(new TimeoutRunnerError()), timeout);
}
}
exports.TimeoutRunner = TimeoutRunner;
async function raceAgainstTimeout(cb, timeout) {
const runner = new TimeoutRunner(timeout);
try {
return {
result: await runner.run(cb),
timedOut: false
};
} catch (e) {
if (e instanceof TimeoutRunnerError) return {
timedOut: true
};
throw e;
}
}
async function pollAgainstTimeout(callback, timeout, pollIntervals = [100, 250, 500, 1000]) {
var _pollIntervals$pop;
const startTime = (0, _.monotonicTime)();
const lastPollInterval = (_pollIntervals$pop = pollIntervals.pop()) !== null && _pollIntervals$pop !== void 0 ? _pollIntervals$pop : 1000;
let lastResult;
const wrappedCallback = () => Promise.resolve().then(callback);
while (true) {
var _shift;
const elapsed = (0, _.monotonicTime)() - startTime;
if (timeout !== 0 && elapsed >= timeout) break;
const received = timeout !== 0 ? await raceAgainstTimeout(wrappedCallback, timeout - elapsed) : await wrappedCallback().then(value => ({
result: value,
timedOut: false
}));
if (received.timedOut) break;
lastResult = received.result.result;
if (!received.result.continuePolling) return {
result: lastResult,
timedOut: false
};
const interval = (_shift = pollIntervals.shift()) !== null && _shift !== void 0 ? _shift : lastPollInterval;
if (timeout !== 0 && startTime + timeout <= (0, _.monotonicTime)() + interval) break;
await new Promise(x => setTimeout(x, interval));
}
return {
timedOut: true,
result: lastResult
};
}

View File

@@ -0,0 +1,174 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.createAfterActionTraceEventForStep = createAfterActionTraceEventForStep;
exports.createBeforeActionTraceEventForStep = createBeforeActionTraceEventForStep;
exports.mergeTraceFiles = mergeTraceFiles;
exports.saveTraceFile = saveTraceFile;
exports.serializeClientSideCallMetadata = serializeClientSideCallMetadata;
var _fs = _interopRequireDefault(require("fs"));
var _zipBundle = require("../zipBundle");
var _manualPromise = require("./manualPromise");
var _crypto = require("./crypto");
var _time = require("./time");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* Copyright (c) Microsoft Corporation.
*
* 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.
*/
function serializeClientSideCallMetadata(metadatas) {
const fileNames = new Map();
const stacks = [];
for (const m of metadatas) {
if (!m.stack || !m.stack.length) continue;
const stack = [];
for (const frame of m.stack) {
let ordinal = fileNames.get(frame.file);
if (typeof ordinal !== 'number') {
ordinal = fileNames.size;
fileNames.set(frame.file, ordinal);
}
const stackFrame = [ordinal, frame.line || 0, frame.column || 0, frame.function || ''];
stack.push(stackFrame);
}
stacks.push([m.id, stack]);
}
return {
files: [...fileNames.keys()],
stacks
};
}
async function mergeTraceFiles(fileName, temporaryTraceFiles) {
if (temporaryTraceFiles.length === 1) {
await _fs.default.promises.rename(temporaryTraceFiles[0], fileName);
return;
}
const mergePromise = new _manualPromise.ManualPromise();
const zipFile = new _zipBundle.yazl.ZipFile();
const entryNames = new Set();
zipFile.on('error', error => mergePromise.reject(error));
for (let i = 0; i < temporaryTraceFiles.length; ++i) {
const tempFile = temporaryTraceFiles[i];
const promise = new _manualPromise.ManualPromise();
_zipBundle.yauzl.open(tempFile, (err, inZipFile) => {
if (err) {
promise.reject(err);
return;
}
let pendingEntries = inZipFile.entryCount;
inZipFile.on('entry', entry => {
let entryName = entry.fileName;
if (entry.fileName.match(/[\d-]*trace./)) entryName = i + '-' + entry.fileName;
inZipFile.openReadStream(entry, (err, readStream) => {
if (err) {
promise.reject(err);
return;
}
if (!entryNames.has(entryName)) {
entryNames.add(entryName);
zipFile.addReadStream(readStream, entryName);
}
if (--pendingEntries === 0) promise.resolve();
});
});
});
await promise;
}
zipFile.end(undefined, () => {
zipFile.outputStream.pipe(_fs.default.createWriteStream(fileName)).on('close', () => {
Promise.all(temporaryTraceFiles.map(tempFile => _fs.default.promises.unlink(tempFile))).then(() => {
mergePromise.resolve();
});
});
});
await mergePromise;
}
async function saveTraceFile(fileName, traceEvents, saveSources) {
const zipFile = new _zipBundle.yazl.ZipFile();
if (saveSources) {
const sourceFiles = new Set();
for (const event of traceEvents) {
if (event.type === 'before') {
for (const frame of event.stack || []) sourceFiles.add(frame.file);
}
}
for (const sourceFile of sourceFiles) {
await _fs.default.promises.readFile(sourceFile, 'utf8').then(source => {
zipFile.addBuffer(Buffer.from(source), 'resources/src@' + (0, _crypto.calculateSha1)(sourceFile) + '.txt');
}).catch(() => {});
}
}
const sha1s = new Set();
for (const event of traceEvents.filter(e => e.type === 'after')) {
for (const attachment of event.attachments || []) {
let contentPromise;
if (attachment.path) contentPromise = _fs.default.promises.readFile(attachment.path);else if (attachment.base64) contentPromise = Promise.resolve(Buffer.from(attachment.base64, 'base64'));
if (!contentPromise) continue;
const content = await contentPromise;
const sha1 = (0, _crypto.calculateSha1)(content);
attachment.sha1 = sha1;
delete attachment.path;
delete attachment.base64;
if (sha1s.has(sha1)) continue;
sha1s.add(sha1);
zipFile.addBuffer(content, 'resources/' + sha1);
}
}
const traceContent = Buffer.from(traceEvents.map(e => JSON.stringify(e)).join('\n'));
zipFile.addBuffer(traceContent, 'trace.trace');
await new Promise(f => {
zipFile.end(undefined, () => {
zipFile.outputStream.pipe(_fs.default.createWriteStream(fileName)).on('close', f);
});
});
}
function createBeforeActionTraceEventForStep(callId, parentId, apiName, params, wallTime, stack) {
return {
type: 'before',
callId,
parentId,
wallTime,
startTime: (0, _time.monotonicTime)(),
class: 'Test',
method: 'step',
apiName,
params: Object.fromEntries(Object.entries(params || {}).map(([name, value]) => [name, generatePreview(value)])),
stack
};
}
function createAfterActionTraceEventForStep(callId, attachments, error) {
return {
type: 'after',
callId,
endTime: (0, _time.monotonicTime)(),
log: [],
attachments,
error
};
}
function generatePreview(value, visited = new Set()) {
if (visited.has(value)) return '';
visited.add(value);
if (typeof value === 'string') return value;
if (typeof value === 'number') return value.toString();
if (typeof value === 'boolean') return value.toString();
if (value === null) return 'null';
if (value === undefined) return 'undefined';
if (Array.isArray(value)) return '[' + value.map(v => generatePreview(v, visited)).join(', ') + ']';
if (typeof value === 'object') return 'Object';
return String(value);
}

View File

@@ -0,0 +1,91 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.getEmbedderName = getEmbedderName;
exports.getPlaywrightVersion = getPlaywrightVersion;
exports.getUserAgent = getUserAgent;
var _child_process = require("child_process");
var _os = _interopRequireDefault(require("os"));
var _linuxUtils = require("../utils/linuxUtils");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* Copyright (c) Microsoft Corporation.
*
* 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.
*/
let cachedUserAgent;
function getUserAgent() {
if (cachedUserAgent) return cachedUserAgent;
try {
cachedUserAgent = determineUserAgent();
} catch (e) {
cachedUserAgent = 'Playwright/unknown';
}
return cachedUserAgent;
}
function determineUserAgent() {
let osIdentifier = 'unknown';
let osVersion = 'unknown';
if (process.platform === 'win32') {
const version = _os.default.release().split('.');
osIdentifier = 'windows';
osVersion = `${version[0]}.${version[1]}`;
} else if (process.platform === 'darwin') {
const version = (0, _child_process.execSync)('sw_vers -productVersion', {
stdio: ['ignore', 'pipe', 'ignore']
}).toString().trim().split('.');
osIdentifier = 'macOS';
osVersion = `${version[0]}.${version[1]}`;
} else if (process.platform === 'linux') {
const distroInfo = (0, _linuxUtils.getLinuxDistributionInfoSync)();
if (distroInfo) {
osIdentifier = distroInfo.id || 'linux';
osVersion = distroInfo.version || 'unknown';
} else {
// Linux distribution without /etc/os-release.
// Default to linux/unknown.
osIdentifier = 'linux';
}
}
const additionalTokens = [];
if (process.env.CI) additionalTokens.push('CI/1');
const serializedTokens = additionalTokens.length ? ' ' + additionalTokens.join(' ') : '';
const {
embedderName,
embedderVersion
} = getEmbedderName();
return `Playwright/${getPlaywrightVersion()} (${_os.default.arch()}; ${osIdentifier} ${osVersion}) ${embedderName}/${embedderVersion}${serializedTokens}`;
}
function getEmbedderName() {
let embedderName = 'unknown';
let embedderVersion = 'unknown';
if (!process.env.PW_LANG_NAME) {
embedderName = 'node';
embedderVersion = process.version.substring(1).split('.').slice(0, 2).join('.');
} else if (['node', 'python', 'java', 'csharp'].includes(process.env.PW_LANG_NAME)) {
var _process$env$PW_LANG_;
embedderName = process.env.PW_LANG_NAME;
embedderVersion = (_process$env$PW_LANG_ = process.env.PW_LANG_NAME_VERSION) !== null && _process$env$PW_LANG_ !== void 0 ? _process$env$PW_LANG_ : 'unknown';
}
return {
embedderName,
embedderVersion
};
}
function getPlaywrightVersion(majorMinorOnly = false) {
const packageJson = require('./../../package.json');
return majorMinorOnly ? packageJson.version.split('.').slice(0, 2).join('.') : packageJson.version;
}

View File

@@ -0,0 +1,75 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.ZipFile = void 0;
var _zipBundle = require("../zipBundle");
/**
* Copyright (c) Microsoft Corporation.
*
* 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.
*/
class ZipFile {
constructor(fileName) {
this._fileName = void 0;
this._zipFile = void 0;
this._entries = new Map();
this._openedPromise = void 0;
this._fileName = fileName;
this._openedPromise = this._open();
}
async _open() {
await new Promise((fulfill, reject) => {
_zipBundle.yauzl.open(this._fileName, {
autoClose: false
}, (e, z) => {
if (e) {
reject(e);
return;
}
this._zipFile = z;
this._zipFile.on('entry', entry => {
this._entries.set(entry.fileName, entry);
});
this._zipFile.on('end', fulfill);
});
});
}
async entries() {
await this._openedPromise;
return [...this._entries.keys()];
}
async read(entryPath) {
await this._openedPromise;
const entry = this._entries.get(entryPath);
if (!entry) throw new Error(`${entryPath} not found in file ${this._fileName}`);
return new Promise((resolve, reject) => {
this._zipFile.openReadStream(entry, (error, readStream) => {
if (error || !readStream) {
reject(error || 'Entry not found');
return;
}
const buffers = [];
readStream.on('data', data => buffers.push(data));
readStream.on('end', () => resolve(Buffer.concat(buffers)));
});
});
}
close() {
var _this$_zipFile;
(_this$_zipFile = this._zipFile) === null || _this$_zipFile === void 0 ? void 0 : _this$_zipFile.close();
}
}
exports.ZipFile = ZipFile;

View File

@@ -0,0 +1,100 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.runWithFinally = runWithFinally;
exports.zones = void 0;
var _stackTrace = require("./stackTrace");
/**
* Copyright (c) Microsoft Corporation.
*
* 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.
*/
class ZoneManager {
constructor() {
this.lastZoneId = 0;
this._zones = new Map();
}
run(type, data, func) {
return new Zone(this, ++this.lastZoneId, type, data).run(func);
}
zoneData(type, rawStack) {
for (const line of rawStack) {
for (const zoneId of zoneIds(line)) {
const zone = this._zones.get(zoneId);
if (zone && zone.type === type) return zone.data;
}
}
return null;
}
preserve(callback) {
const rawStack = (0, _stackTrace.captureRawStack)();
const refs = [];
for (const line of rawStack) refs.push(...zoneIds(line));
Object.defineProperty(callback, 'name', {
value: `__PWZONE__[${refs.join(',')}]-refs`
});
return callback();
}
}
function zoneIds(line) {
const index = line.indexOf('__PWZONE__[');
if (index === -1) return [];
return line.substring(index + '__PWZONE__['.length, line.indexOf(']', index)).split(',').map(s => +s);
}
class Zone {
constructor(manager, id, type, data) {
this._manager = void 0;
this.id = void 0;
this.type = void 0;
this.data = void 0;
this.wallTime = void 0;
this._manager = manager;
this.id = id;
this.type = type;
this.data = data;
this.wallTime = Date.now();
}
run(func) {
this._manager._zones.set(this.id, this);
Object.defineProperty(func, 'name', {
value: `__PWZONE__[${this.id}]-${this.type}`
});
return runWithFinally(() => func(this.data), () => {
this._manager._zones.delete(this.id);
});
}
}
function runWithFinally(func, finallyFunc) {
try {
const result = func();
if (result instanceof Promise) {
return result.then(r => {
finallyFunc();
return r;
}).catch(e => {
finallyFunc();
throw e;
});
}
finallyFunc();
return result;
} catch (e) {
finallyFunc();
throw e;
}
}
const zones = new ZoneManager();
exports.zones = zones;