chore(deps): update selenium-webdriver to 4 (#3205)

* chore(deps): update selenium-webdriver to 4

refactor: use async/await

* fix(screenshotter): disable static watch
This commit is contained in:
ylemkimon
2021-08-29 02:42:12 +09:00
committed by GitHub
parent 0084c899f3
commit abe3ab7eb8
3 changed files with 237 additions and 329 deletions

View File

@@ -2,6 +2,7 @@
"use strict";
const childProcess = require("child_process");
const util = require("util");
const fs = require("fs-extra");
const jspngopt = require("jspngopt");
const net = require("net");
@@ -9,6 +10,9 @@ const os = require("os");
const pako = require("pako");
const path = require("path");
const got = require("got");
const pRetry = require('p-retry');
const execFile = util.promisify(childProcess.execFile);
const selenium = require("selenium-webdriver");
const firefox = require("selenium-webdriver/firefox");
@@ -112,15 +116,6 @@ if (opts.browserstack) {
//////////////////////////////////////////////////////////////////////
// Work out connection to selenium docker container
function check(err) {
if (!err) {
return;
}
console.error(err);
console.error(err.stack);
process.exit(1);
}
function cmd() {
const args = Array.prototype.slice.call(arguments);
const cmd = args.shift();
@@ -184,8 +179,36 @@ if (seleniumURL) {
console.log("Selenium driver in local session");
}
process.nextTick(startServer);
let attempts = 0;
(async() => {
if (!(katexURL || katexPort)) {
await pRetry(startServer, {retries: 50, minTimeout: 100});
}
if (opts.seleniumProxy) {
driver = await getProxyDriver();
} else {
if (opts.browserstack) {
await startBrowserstackLocal();
}
if (seleniumIP) {
await pRetry(tryConnect, {retries: 50, minTimeout: 100});
}
driver = buildDriver();
}
await setupDriver();
await findHostIP();
await takeScreenshots();
await driver.quit();
await devServer.stop();
if (bsLocal) {
const bsLocalStop = util.promisify(bsLocal.stop);
await bsLocalStop();
}
process.exit(exitStatus);
})().catch(err => {
console.error(err);
process.exit(1);
});
//////////////////////////////////////////////////////////////////////
// Start up development server
@@ -195,13 +218,8 @@ let coverageMap;
const minPort = 32768;
const maxPort = 61000;
function startServer() {
if (katexURL || katexPort) {
process.nextTick(tryConnect);
return;
}
const port = Math.floor(Math.random() * (maxPort - minPort)) + minPort;
async function startServer() {
katexPort = Math.floor(Math.random() * (maxPort - minPort)) + minPort;
if (opts.coverage) {
coverageMap = istanbulLibCoverage.createCoverageMap({});
webpackConfig.module.rules[0].use = {
@@ -216,67 +234,50 @@ function startServer() {
}
const config = {
...webpackConfig.devServer,
port,
static: [{directory: process.cwd(), watch: false}],
port: katexPort,
hot: false,
liveReload: false,
client: false,
};
const compiler = webpack(webpackConfig);
const wds = new WebpackDevServer(config, compiler);
wds.start(port).then(() => {
devServer = wds;
katexPort = port;
attempts = 0;
process.nextTick(opts.seleniumProxy ? getProxyDriver
: opts.browserstack ? startBrowserstackLocal : tryConnect);
})
.catch((err) => {
if (devServer !== null) { // error after we started listening
throw err;
} else if (++attempts > 50) {
throw new Error("Failed to start up dev server");
} else {
process.nextTick(startServer);
}
});
devServer = new WebpackDevServer(config, compiler);
await devServer.start(katexPort);
}
// Start Browserstack Local connection
function startBrowserstackLocal() {
async function startBrowserstackLocal() {
// unique identifier for the session
const localIdentifier = process.env.CIRCLE_BUILD_NUM || "p" + katexPort;
opts.seleniumCapabilities["browserstack.localIdentifier"] = localIdentifier;
bsLocal = new browserstack.Local();
bsLocal.start({localIdentifier}, function(err) {
if (err) {
throw err;
}
process.nextTick(tryConnect);
await new Promise((resolve, reject) => {
bsLocal.start({localIdentifier}, err => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
}
//////////////////////////////////////////////////////////////////////
// Wait for container to become ready
function tryConnect() {
if (!seleniumIP) {
process.nextTick(buildDriver);
return;
}
const sock = net.connect({
host: seleniumIP,
port: +seleniumPort,
});
sock.on("connect", function() {
sock.end();
attempts = 0;
process.nextTick(buildDriver);
}).on("error", function() {
if (++attempts > 50) {
throw new Error("Failed to connect selenium server.");
}
setTimeout(tryConnect, 200);
async function tryConnect() {
return new Promise((resolve, reject) => {
const sock = net.connect({
host: seleniumIP,
port: +seleniumPort,
});
sock.on("connect", function() {
sock.end();
resolve();
}).on("error", function(err) {
reject(err);
});
});
}
@@ -288,11 +289,10 @@ let driverReady = false;
function buildDriver() {
const builder = new selenium.Builder().forBrowser(opts.browser);
if (opts.browser === "firefox") {
const ffProfile = new firefox.Profile();
ffProfile.setPreference(
const ffOptions = new firefox.Options();
ffOptions.setPreference(
"browser.startup.homepage_override.mstone", "ignore");
ffProfile.setPreference("browser.startup.page", 0);
const ffOptions = new firefox.Options().setProfile(ffProfile);
ffOptions.setPreference("browser.startup.page", 0);
builder.setFirefoxOptions(ffOptions);
} else if (opts.browser === "chrome") {
// https://stackoverflow.com/questions/48450594/selenium-timed-out-receiving-message-from-renderer
@@ -305,39 +305,35 @@ function buildDriver() {
if (opts.seleniumCapabilities) {
builder.withCapabilities(opts.seleniumCapabilities);
}
driver = builder.build();
setupDriver();
return builder.build();
}
function getProxyDriver() {
got.post(opts.seleniumProxy, {
async function getProxyDriver() {
const {body} = await got.post(opts.seleniumProxy, {
json: {
browserstack: opts.browserstack,
capabilities: opts.seleniumCapabilities,
seleniumURL,
},
responseType: 'json',
}).then(({body}) => {
const session = new selenium.Session(body.id, body.capabilities);
const client = Promise.resolve(seleniumURL)
.then(url => new seleniumHttp.HttpClient(url));
const executor = new seleniumHttp.Executor(client);
driver = new selenium.WebDriver(session, executor);
setupDriver();
});
const session = new selenium.Session(body.id, body.capabilities);
const client = Promise.resolve(new seleniumHttp.HttpClient(seleniumURL));
const executor = new seleniumHttp.Executor(client);
return new selenium.WebDriver(session, executor);
}
function setupDriver() {
driver.manage().timeouts().setScriptTimeout(3000).then(function() {
let html = '<!DOCTYPE html>' +
'<html><head><style type="text/css">html,body{' +
'width:100%;height:100%;margin:0;padding:0;overflow:hidden;' +
'}</style></head><body><p>Test</p></body></html>';
html = "data:text/html," + encodeURIComponent(html);
return driver.get(html);
}).then(function() {
setSize(targetW, targetH);
});
async function setupDriver() {
await driver.manage().setTimeouts({script: 5000});
let html = '<!DOCTYPE html>' +
'<html><head><style type="text/css">html,body{' +
'width:100%;height:100%;margin:0;padding:0;overflow:hidden;' +
'}</style></head><body><p>Test</p></body></html>';
html = "data:text/html," + encodeURIComponent(html);
await driver.get(html);
await setSize(targetW, targetH);
}
//////////////////////////////////////////////////////////////////////
@@ -345,22 +341,20 @@ function setupDriver() {
const targetW = 1024;
const targetH = 768;
function setSize(reqW, reqH) {
return driver.manage().window().setSize(reqW, reqH).then(function() {
return driver.takeScreenshot();
}).then(function(img) {
img = imageDimensions(img);
const actualW = img.width;
const actualH = img.height;
if (actualW === targetW && actualH === targetH) {
findHostIP();
return;
}
if (++attempts > opts.attempts) {
throw new Error("Failed to set window size correctly.");
}
return setSize(targetW + reqW - actualW, targetH + reqH - actualH);
}, check);
let attempts = 0;
async function setSize(width, height) {
await driver.manage().window().setRect({width, height});
let img = await driver.takeScreenshot();
img = imageDimensions(img);
const actualW = img.width;
const actualH = img.height;
if (actualW === targetW && actualH === targetH) {
return;
}
if (++attempts > opts.attempts) {
throw new Error("Failed to set window size correctly.");
}
return setSize(targetW + width - actualW, targetH + height - actualH);
}
function imageDimensions(img) {
@@ -375,7 +369,7 @@ function imageDimensions(img) {
//////////////////////////////////////////////////////////////////////
// Work out how to connect to host KaTeX server
function findHostIP() {
async function findHostIP() {
if (!katexIP) {
katexIP = "localhost";
}
@@ -384,21 +378,22 @@ function findHostIP() {
katexURL = "http://" + katexIP + ":" + katexPort + "/";
console.log("KaTeX URL is " + katexURL);
}
process.nextTick(takeScreenshots);
return;
}
// Now we need to find an IP the container can connect to.
// First, install a server component to get notified of successful connects
devServer.app.get("/ss-connect.js", function(req, res, next) {
if (!katexURL) {
katexIP = req.query.ip;
katexURL = "http://" + katexIP + ":" + katexPort + "/";
console.log("KaTeX URL is " + katexURL);
process.nextTick(takeScreenshots);
}
res.setHeader("Content-Type", "text/javascript");
res.send("//OK");
const connect = new Promise((resolve) => {
devServer.app.get("/ss-connect.js", function(req, res, next) {
if (!katexURL) {
katexIP = req.query.ip;
katexURL = "http://" + katexIP + ":" + katexPort + "/";
console.log("KaTeX URL is " + katexURL);
}
res.setHeader("Content-Type", "text/javascript");
res.send("//OK");
resolve();
});
});
// Next, enumerate all network addresses
@@ -427,22 +422,41 @@ function findHostIP() {
}).join("\n");
html += "\n</body></html>";
html = "data:text/html," + encodeURIComponent(html);
driver.get(html);
await driver.get(html);
await connect;
}
//////////////////////////////////////////////////////////////////////
// Take the screenshots
let countdown = listOfCases.length;
let exitStatus = 0;
const listOfFailed = [];
function takeScreenshots() {
listOfCases.forEach(takeScreenshot);
async function takeScreenshots() {
for (const key of listOfCases) {
await takeScreenshot(key);
}
if (listOfFailed.length) {
console.error("Failed: " + listOfFailed.join(" "));
}
if (opts.diff) {
console.log("Diffs have been generated in: " + diffDir);
}
if (opts.new) {
console.log("New screenshots have been generated in: " + newDir);
}
if (opts.coverage) {
await collectCoverage();
const context = istanbulLibReport.createContext({coverageMap});
['json', 'text', 'lcov'].forEach(fmt => {
const report = istanbulReports.create(fmt);
report.execute(context);
});
}
}
function takeScreenshot(key) {
async function takeScreenshot(key) {
const itm = data[key];
if (!itm) {
console.error("Test case " + key + " not known!");
@@ -450,70 +464,43 @@ function takeScreenshot(key) {
if (exitStatus === 0) {
exitStatus = 1;
}
oneDone();
return;
}
let file = path.join(dstDir, key + "-" + opts.browser + ".png");
let retry = 0;
let loadExpected = null;
if (opts.verify) {
loadExpected = fs.readFile(file);
let expected = null;
if (opts.verify && await fs.pathExists(file)) {
expected = await fs.readFile(file);
}
const url = katexURL + "test/screenshotter/test.html?" + itm.query;
driver.call(loadMath);
let buf;
function loadMath() {
while (++retry <= opts.attempts) {
if (!opts.reload && driverReady) {
driver.executeScript(
await driver.executeScript(
"handle_search_string(" +
JSON.stringify("?" + itm.query) + ");")
.then(waitThenScreenshot);
} else if (opts.coverage) {
// collect coverage before reloading
collectCoverage().then(function() {
return driver.get(url).then(loadFonts);
});
JSON.stringify("?" + itm.query) + ");");
} else {
driver.get(url).then(loadFonts);
if (opts.coverage) {
// collect coverage before reloading
await collectCoverage();
}
await driver.get(url);
await driver.executeAsyncScript(
"var callback = arguments[arguments.length - 1]; " +
"load_fonts_and_images(callback);");
driverReady = true;
}
}
function collectCoverage() {
return driver.executeScript('return window.__coverage__;')
.then(function(result) {
if (result) {
coverageMap.merge(result);
}
});
}
function loadFonts() {
driver.executeAsyncScript(
"var callback = arguments[arguments.length - 1]; " +
"load_fonts_and_images(callback);")
.then(waitThenScreenshot);
}
function waitThenScreenshot() {
driverReady = true;
if (opts.wait) {
browserSideWait(1000 * opts.wait);
await browserSideWait(1000 * opts.wait);
}
const promise = driver.takeScreenshot().then(haveScreenshot);
if (retry === 0) {
// The `oneDone` promise remains outstanding if we retry, so
// don't re-add it
promise.then(oneDone, check);
}
}
function haveScreenshot(img) {
let img = await driver.takeScreenshot();
img = imageDimensions(img);
if (img.width !== targetW || img.height !== targetH) {
throw new Error("Expected " + targetW + " x " + targetH +
", got " + img.width + "x" + img.height);
console.error("Expected " + targetW + " x " + targetH +
", got " + img.width + "x" + img.height);
await setSize(targetW, targetH);
}
if (key === "Lap" && opts.browser === "firefox" &&
img.buf[0x32] === 0xf8) {
@@ -525,141 +512,77 @@ function takeScreenshot(key) {
*/
key += "_alt";
file = path.join(dstDir, key + "-" + opts.browser + ".png");
if (loadExpected) {
loadExpected = fs.readFile(file);
if (expected) {
expected = await fs.readFile(file);
}
}
const opt = new jspngopt.Optimizer({
pako: pako,
});
const buf = opt.bufferSync(img.buf);
if (loadExpected) {
return loadExpected.then(function(expected) {
if (!buf.equals(expected)) {
if (++retry >= opts.attempts) {
console.error("FAIL! " + key);
listOfFailed.push(key);
exitStatus = 3;
if (opts.diff || opts.new) {
return saveFailedScreenshot(key, buf);
}
} else {
console.log("error " + key);
browserSideWait(300 * retry);
if (retry > 1) {
driverReady = false; // reload fully
}
return driver.call(loadMath);
}
} else {
console.log("* ok " + key);
}
});
buf = opt.bufferSync(img.buf);
if (expected) {
if (buf.equals(expected)) {
console.log("* ok " + key);
return;
}
console.log("error " + key);
await browserSideWait(300 * retry);
if (retry > 1) {
driverReady = false; // reload fully
}
} else {
return fs.writeFile(file, buf).then(function() {
console.log(key);
});
await fs.writeFile(file, buf);
console.log(key);
return;
}
}
function saveFailedScreenshot(key, buf) {
console.error("FAIL! " + key);
listOfFailed.push(key);
exitStatus = 3;
if (opts.diff || opts.new) {
const filenamePrefix = key + "-" + opts.browser;
const outputDir = opts.new ? newDir : diffDir;
const baseFile = path.join(dstDir, filenamePrefix + ".png");
const diffFile = path.join(diffDir, filenamePrefix + "-diff.png");
const bufFile = path.join(outputDir, filenamePrefix + ".png");
let promise = fs.ensureDir(outputDir)
.then(function() {
return fs.writeFile(bufFile, buf);
});
await fs.ensureDir(outputDir);
await fs.writeFile(bufFile, buf);
if (opts.diff) {
promise = promise.then(fs.ensureDir(diffDir))
.then(function() {
return execFile("convert", [
"-fill", "white",
// First image: saved screenshot in red
"(", baseFile, "-colorize", "100,0,0", ")",
// Second image: new screenshot in green
"(", bufFile, "-colorize", "0,80,0", ")",
// Composite them
"-compose", "darken", "-composite",
"-trim", // remove everything with the same color as
// the corners
diffFile, // output file name
]);
});
await fs.ensureDir(diffDir);
await execFile("convert", [
"-fill", "white",
// First image: saved screenshot in red
"(", baseFile, "-colorize", "100,0,0", ")",
// Second image: new screenshot in green
"(", bufFile, "-colorize", "0,80,0", ")",
// Composite them
"-compose", "darken", "-composite",
"-trim", // remove everything with the same color as
// the corners
diffFile, // output file name
]);
}
if (!opts.new) {
promise = promise.then(function() {
return fs.unlink(bufFile);
});
await fs.unlink(bufFile);
}
return promise;
}
function oneDone() {
if (--countdown === 0) {
if (listOfFailed.length) {
console.error("Failed: " + listOfFailed.join(" "));
}
if (opts.diff) {
console.log("Diffs have been generated in: " + diffDir);
}
if (opts.new) {
console.log("New screenshots have been generated in: " + newDir);
}
if (opts.coverage) {
collectCoverage().then(function() {
const context = istanbulLibReport.createContext({coverageMap});
['json', 'text', 'lcov'].forEach(fmt => {
const report = istanbulReports.create(fmt);
report.execute(context);
});
done();
});
return;
}
done();
}
}
function done() {
// devServer.close(cb) will take too long.
driver.quit().then(() => {
if (bsLocal) {
bsLocal.stop(() => {
process.exit(exitStatus);
});
} else {
process.exit(exitStatus);
}
});
}
}
// Wait using a timeout call in the browser, to ensure that the wait
// time doesn't start before the page has reportedly been loaded.
function browserSideWait(milliseconds) {
async function browserSideWait(milliseconds) {
// The last argument (arguments[1] here) is the callback to selenium
return driver.executeAsyncScript(
await driver.executeAsyncScript(
"window.setTimeout(arguments[1], arguments[0]);",
milliseconds);
}
// Execute a given command, and return a promise to its output.
function execFile(cmd, args, opts) {
return new Promise(function(resolve, reject) {
childProcess.execFile(cmd, args, opts, function(err, stdout, stderr) {
if (err) {
console.error("Error executing " + cmd + " " + args.join(" "));
console.error(stdout + stderr);
err.stdout = stdout;
err.stderr = stderr;
reject(err);
} else {
resolve(stdout);
}
});
});
async function collectCoverage() {
const result = await driver.executeScript('return window.__coverage__;');
if (result) {
coverageMap.merge(result);
}
}

View File

@@ -65,6 +65,7 @@
"less-loader": "^10.0.0",
"mini-css-extract-plugin": "^2.0.0",
"mkdirp": "^1.0.4",
"p-retry": "^4.6.1",
"pako": "^2.0.0",
"postcss": "^8.0.0",
"postcss-loader": "^6.0.0",
@@ -73,7 +74,7 @@
"query-string": "^7.0.0",
"rimraf": "^3.0.2",
"rollup": "^2.21.0",
"selenium-webdriver": "^3.6.0",
"selenium-webdriver": "^4.0.0-beta.4",
"semantic-release": "^17.4.1",
"sri-toolbox": "^0.2.0",
"style-loader": "^3.0.0",

View File

@@ -8391,15 +8391,15 @@ __metadata:
languageName: node
linkType: hard
"jszip@npm:^3.1.3":
version: 3.5.0
resolution: "jszip@npm:3.5.0"
"jszip@npm:^3.6.0":
version: 3.7.1
resolution: "jszip@npm:3.7.1"
dependencies:
lie: ~3.3.0
pako: ~1.0.2
readable-stream: ~2.3.6
set-immediate-shim: ~1.0.1
checksum: e4c555273063ac1284f387e440c9b0ccef99af3dd87f3af4a7fc1b4396f33cc45fa10afdb059105dd15a61763288c99e53a5e078dd0af9183a83e694e005d054
checksum: 67d737a82b294cc102e7451e32d5acbbab29860399be460cae598084327e6f2ea0c9bca2d3dad701da6a75ddf77f34c6a1dd7db0c3d5c0fec5998b7e56d6d59d
languageName: node
linkType: hard
@@ -8455,6 +8455,7 @@ __metadata:
less-loader: ^10.0.0
mini-css-extract-plugin: ^2.0.0
mkdirp: ^1.0.4
p-retry: ^4.6.1
pako: ^2.0.0
postcss: ^8.0.0
postcss-loader: ^6.0.0
@@ -8463,7 +8464,7 @@ __metadata:
query-string: ^7.0.0
rimraf: ^3.0.2
rollup: ^2.21.0
selenium-webdriver: ^3.6.0
selenium-webdriver: ^4.0.0-beta.4
semantic-release: ^17.4.1
sri-toolbox: ^0.2.0
style-loader: ^3.0.0
@@ -10483,7 +10484,7 @@ __metadata:
languageName: node
linkType: hard
"os-tmpdir@npm:^1.0.0, os-tmpdir@npm:~1.0.1":
"os-tmpdir@npm:^1.0.0":
version: 1.0.2
resolution: "os-tmpdir@npm:1.0.2"
checksum: 5666560f7b9f10182548bf7013883265be33620b1c1b4a4d405c25be2636f970c5488ff3e6c48de75b55d02bde037249fe5dbfbb4c0fb7714953d56aed062e6d
@@ -10639,7 +10640,7 @@ __metadata:
languageName: node
linkType: hard
"p-retry@npm:^4.0.0, p-retry@npm:^4.5.0":
"p-retry@npm:^4.0.0, p-retry@npm:^4.5.0, p-retry@npm:^4.6.1":
version: 4.6.1
resolution: "p-retry@npm:4.6.1"
dependencies:
@@ -12785,7 +12786,7 @@ resolve@^2.0.0-next.3:
languageName: node
linkType: hard
"sax@npm:>=0.6.0, sax@npm:^1.2.4":
"sax@npm:^1.2.4":
version: 1.2.4
resolution: "sax@npm:1.2.4"
checksum: d3df7d32b897a2c2f28e941f732c71ba90e27c24f62ee918bd4d9a8cfb3553f2f81e5493c7f0be94a11c1911b643a9108f231dd6f60df3fa9586b5d2e3e9e1fe
@@ -12830,15 +12831,15 @@ resolve@^2.0.0-next.3:
languageName: node
linkType: hard
"selenium-webdriver@npm:^3.6.0":
version: 3.6.0
resolution: "selenium-webdriver@npm:3.6.0"
"selenium-webdriver@npm:^4.0.0-beta.4":
version: 4.0.0-beta.4
resolution: "selenium-webdriver@npm:4.0.0-beta.4"
dependencies:
jszip: ^3.1.3
rimraf: ^2.5.4
tmp: 0.0.30
xml2js: ^0.4.17
checksum: 5bc1045d0205c5aed1f3e3cf8047d3bb677e370e96ae4a8acd172846c07aeb40c031bee5017a7c432bec36e46c5bbce82fe3b40086b7daa4cb31dcaf69daad55
jszip: ^3.6.0
rimraf: ^3.0.2
tmp: ^0.2.1
ws: ">=7.4.6"
checksum: c57ec5b41f8ff4a81280d548f95e899a01f6cf6b0b46fb4f7a92440a710836a2994368561c0be43abd301f76c293e29dca68d959a9ae6c15fb03f5a040dee240
languageName: node
linkType: hard
@@ -14128,12 +14129,12 @@ resolve@^2.0.0-next.3:
languageName: node
linkType: hard
"tmp@npm:0.0.30":
version: 0.0.30
resolution: "tmp@npm:0.0.30"
"tmp@npm:^0.2.1":
version: 0.2.1
resolution: "tmp@npm:0.2.1"
dependencies:
os-tmpdir: ~1.0.1
checksum: d3e97e8e73b2d2dfff9916072004088b4737c67d11ea255d0ccc8584f252b253b60ecf04122b21848ec46ad5a92e31febc6d6a3068f6c8a20c9b0e23a802e78d
rimraf: ^3.0.0
checksum: 8b1214654182575124498c87ca986ac53dc76ff36e8f0e0b67139a8d221eaecfdec108c0e6ec54d76f49f1f72ab9325500b246f562b926f85bcdfca8bf35df9e
languageName: node
linkType: hard
@@ -15158,6 +15159,21 @@ resolve@^2.0.0-next.3:
languageName: node
linkType: hard
"ws@npm:>=7.4.6, ws@npm:^8.1.0":
version: 8.2.0
resolution: "ws@npm:8.2.0"
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: ^5.0.2
peerDependenciesMeta:
bufferutil:
optional: true
utf-8-validate:
optional: true
checksum: 7cd544312a48dafcb8158c9b4e5f20986cce980d516e0ef0602665911b0e95c5e0dea2846a4bb3153a1e2c839aa3d92fb7e69dd864fe432e881eee9d4e8cf70b
languageName: node
linkType: hard
"ws@npm:^7.3.1, ws@npm:^7.4.6":
version: 7.5.3
resolution: "ws@npm:7.5.3"
@@ -15173,21 +15189,6 @@ resolve@^2.0.0-next.3:
languageName: node
linkType: hard
"ws@npm:^8.1.0":
version: 8.2.0
resolution: "ws@npm:8.2.0"
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: ^5.0.2
peerDependenciesMeta:
bufferutil:
optional: true
utf-8-validate:
optional: true
checksum: 7cd544312a48dafcb8158c9b4e5f20986cce980d516e0ef0602665911b0e95c5e0dea2846a4bb3153a1e2c839aa3d92fb7e69dd864fe432e881eee9d4e8cf70b
languageName: node
linkType: hard
"xdg-basedir@npm:^3.0.0":
version: 3.0.0
resolution: "xdg-basedir@npm:3.0.0"
@@ -15202,23 +15203,6 @@ resolve@^2.0.0-next.3:
languageName: node
linkType: hard
"xml2js@npm:^0.4.17":
version: 0.4.23
resolution: "xml2js@npm:0.4.23"
dependencies:
sax: ">=0.6.0"
xmlbuilder: ~11.0.0
checksum: ca0cf2dfbf6deeaae878a891c8fbc0db6fd04398087084edf143cdc83d0509ad0fe199b890f62f39c4415cf60268a27a6aed0d343f0658f8779bd7add690fa98
languageName: node
linkType: hard
"xmlbuilder@npm:~11.0.0":
version: 11.0.1
resolution: "xmlbuilder@npm:11.0.1"
checksum: 7152695e16f1a9976658215abab27e55d08b1b97bca901d58b048d2b6e106b5af31efccbdecf9b07af37c8377d8e7e821b494af10b3a68b0ff4ae60331b415b0
languageName: node
linkType: hard
"xmlchars@npm:^2.2.0":
version: 2.2.0
resolution: "xmlchars@npm:2.2.0"