feat: UI Performance Infra setup v1 (#9835)

Co-authored-by: Satish Gandham <satish@appsmith.com>
This commit is contained in:
Satish Gandham 2021-12-28 16:09:15 +05:30 committed by GitHub
parent 269e3a786d
commit c5abd3dd6f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 1099 additions and 0 deletions

View File

@ -570,3 +570,176 @@ jobs:
return result; return result;
} }
perf-test:
needs: [build, server-build]
# Only run if the build step is successful
if: success()
runs-on: ubuntu-latest
defaults:
run:
working-directory: app/client
shell: bash
strategy:
fail-fast: false
matrix: job:[0]
# Service containers to run with this job. Required for running tests
services:
# Label used to access the service container
redis:
# Docker Hub image for Redis
image: redis
ports:
# Opens tcp port 6379 on the host and service container
- 6379:6379
mongo:
image: mongo
ports:
- 27017:27017
steps:
# Check out merge commitGIT BRANCH
- name: Fork based /ok-to-test-perf checkout
uses: actions/checkout@v2
with:
ref: "refs/pull/${{ github.event.client_payload.pull_request.number }}/merge"
- name: Use Node.js 14.15.4
uses: actions/setup-node@v1
with:
node-version: "14.15.4"
- name: Get yarn cache directory path
id: yarn-dep-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
# Retrieve npm dependencies from cache. After a successful run, these dependencies are cached again
- name: Cache npm dependencies
id: yarn-dep-cache
uses: actions/cache@v2
env:
cache-name: cache-yarn-dependencies
with:
path: |
${{ steps.yarn-dep-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-dep-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-dep-
# Install all the dependencies
- name: Install dependencies
run: yarn install
- name: Download the react build artifact
uses: actions/download-artifact@v2
with:
name: build
path: app/client/build
- name: Download the server build artifact
uses: actions/download-artifact@v2
with:
name: build
path: app/server/dist
# Start server
- name: start server
working-directory: app/server
env:
APPSMITH_MONGODB_URI: "mongodb://localhost:27017/mobtools"
APPSMITH_REDIS_URL: "redis://127.0.0.1:6379"
APPSMITH_ENCRYPTION_PASSWORD: "password"
APPSMITH_ENCRYPTION_SALT: "salt"
APPSMITH_IS_SELF_HOSTED: false
APPSMITH_CLOUD_SERVICES_BASE_URL: https://release-cs.appsmith.com
APPSMITH_CLOUD_SERVICES_USERNAME: ""
APPSMITH_CLOUD_SERVICES_PASSWORD: ""
run: |
ls -l
ls -l scripts/
ls -l dist/
# Run the server in the background and redirect logs to a log file
./scripts/start-dev-server.sh &> server-logs.log &
- name: Wait for 30 seconds for server to start
run: |
sleep 30s
- name: Exit if Server hasnt started
run: |
if [[ `ps -ef | grep "server-1.0-SNAPSHOT" | grep java |wc -l` == 0 ]]; then
echo "Server Not Started";
exit 1;
else
echo "Server Found";
fi
- name: Installing Yarn serve
run: |
yarn global add serve
echo "$(yarn global bin)" >> $GITHUB_PATH
- name: Setting up the perf tests
shell: bash
env:
APPSMITH_SSL_CERTIFICATE: ${{ secrets.APPSMITH_SSL_CERTIFICATE }}
APPSMITH_SSL_KEY: ${{ secrets.APPSMITH_SSL_KEY }}
CYPRESS_URL: ${{ secrets.CYPRESS_URL }}
CYPRESS_USERNAME: ${{ secrets.CYPRESS_USERNAME }}
CYPRESS_PASSWORD: ${{ secrets.CYPRESS_PASSWORD }}
CYPRESS_TESTUSERNAME1: ${{ secrets.CYPRESS_TESTUSERNAME1 }}
CYPRESS_TESTPASSWORD1: ${{ secrets.CYPRESS_TESTPASSWORD1 }}
CYPRESS_TESTUSERNAME2: ${{ secrets.CYPRESS_TESTUSERNAME2 }}
CYPRESS_TESTPASSWORD2: ${{ secrets.CYPRESS_TESTPASSWORD1 }}
CYPRESS_S3_ACCESS_KEY: ${{ secrets.CYPRESS_S3_ACCESS_KEY }}
CYPRESS_S3_SECRET_KEY: ${{ secrets.CYPRESS_S3_SECRET_KEY }}
APPSMITH_DISABLE_TELEMETRY: true
APPSMITH_GOOGLE_MAPS_API_KEY: ${{ secrets.APPSMITH_GOOGLE_MAPS_API_KEY }}
POSTGRES_PASSWORD: postgres
run: |
./cypress/setup-test.sh
- name: Installing performance tests dependencies
working-directory: app/client/perf
shell: bash
run: yarn install
- name: Change test script permissions
working-directory: app/client/perf
run: chmod +x ./start-test.sh
- name: Run performance tests
working-directory: app/client/perf
shell: bash
env:
APPSMITH_SSL_CERTIFICATE: ${{ secrets.APPSMITH_SSL_CERTIFICATE }}
APPSMITH_SSL_KEY: ${{ secrets.APPSMITH_SSL_KEY }}
CYPRESS_TESTUSERNAME1: ${{ secrets.CYPRESS_TESTUSERNAME9 }}
CYPRESS_TESTPASSWORD1: ${{ secrets.CYPRESS_TESTPASSWORD9 }}
APPSMITH_DISABLE_TELEMETRY: true
POSTGRES_PASSWORD: postgres
NODE_TLS_REJECT_UNAUTHORIZED: "0"
run: ./start-test.sh
- uses: actions/upload-artifact@v2
with:
name: performance-summaries
path: app/client/perf/traces
- name: Read summary file
id: read_summary
uses: andstor/file-reader-action@v1
with:
path: app/client/perf/traces/reports/summary.md
- name: Add a comment with the results on the PR with link to workflow run
uses: peter-evans/create-or-update-comment@v1
with:
issue-number: ${{ github.event.client_payload.pull_request.number }}
body: |
UI Performance test run logs and artifacts: <https://github.com/appsmithorg/appsmith/actions/runs/${{ github.run_id }}>.
Commit: `${{ github.event.client_payload.slash_command.sha }}`.
Results: ${{ steps.read_summary.outputs.contents }}

3
.gitignore vendored
View File

@ -15,3 +15,6 @@ package-lock.json
coverage-summary.json coverage-summary.json
app/client/cypress/locators/Widgets.json app/client/cypress/locators/Widgets.json
deploy/ansible/appsmith_playbook/inventory deploy/ansible/appsmith_playbook/inventory
# performance tests
app/client/perf/traces/*

View File

@ -0,0 +1,85 @@
exports.dsl = {
dsl: {
widgetName: "MainContainer",
backgroundColor: "none",
rightColumn: 1936,
snapColumns: 64,
detachFromLayout: true,
widgetId: "0",
topRow: 0,
bottomRow: 1290,
containerStyle: "none",
snapRows: 125,
parentRowSpace: 1,
type: "CANVAS_WIDGET",
canExtend: true,
version: 46,
minHeight: 1292,
parentColumnSpace: 1,
dynamicBindingPathList: [],
leftColumn: 0,
children: [
{
isVisible: true,
text: "<h1>{{Input1.text}}</h1>",
fontSize: "HEADING1",
fontStyle: "BOLD",
textAlign: "LEFT",
textColor: "#231F20",
widgetName: "Text1",
version: 1,
type: "TEXT_WIDGET",
hideCard: false,
displayName: "Text",
key: "4ln743vbxf",
iconSVG: "/static/media/icon.97c59b52.svg",
widgetId: "oylox3e28e",
renderMode: "CANVAS",
isLoading: false,
parentColumnSpace: 30.0625,
parentRowSpace: 10,
leftColumn: 1,
rightColumn: 39,
topRow: 16,
bottomRow: 40,
parentId: "0",
dynamicBindingPathList: [
{
key: "text",
},
],
dynamicTriggerPathList: [],
},
{
isVisible: true,
inputType: "TEXT",
label: "",
widgetName: "Input1",
version: 1,
defaultText: "",
iconAlign: "left",
autoFocus: false,
labelStyle: "",
resetOnSubmit: true,
isRequired: false,
isDisabled: false,
allowCurrencyChange: false,
type: "INPUT_WIDGET",
hideCard: false,
displayName: "Input",
key: "4xvbov2itw",
iconSVG: "/static/media/icon.9f505595.svg",
widgetId: "d454uqlxd0",
renderMode: "CANVAS",
isLoading: false,
parentColumnSpace: 30.0625,
parentRowSpace: 10,
leftColumn: 1,
rightColumn: 12,
topRow: 9,
bottomRow: 13,
parentId: "0",
},
],
},
};

55
app/client/perf/index.js Normal file
View File

@ -0,0 +1,55 @@
const path = require("path");
const Perf = require("./perf.js");
const dsl = require("./dsl/simple-typing").dsl;
var fs = require("fs");
const { summaries } = require("./summary");
// Set the perf directory as APP_ROOT on the global level
global.APP_ROOT = path.resolve(__dirname);
// Create the directory
const dir = `${APP_ROOT}/traces/reports`;
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = 0;
async function testTyping() {
const perf = new Perf({
ignoreHTTPSErrors: true, // @todo Remove it after initial testing
});
await perf.launch();
const page = perf.getPage();
await perf.loadDSL(dsl);
const selector = "input.bp3-input"; // Input selector
await page.waitForSelector(selector);
const input = await page.$(selector);
await perf.startTrace("Edit input");
await page.type(selector, "Hello Appsmith");
await perf.stopTrace();
await perf.startTrace("Clear input");
await input.click({ clickCount: 3 });
await input.press("Backspace");
await perf.stopTrace();
await perf.startTrace("Edit input again");
await page.type(selector, "Howdy satish");
await perf.stopTrace();
await perf.generateReport();
await perf.close();
}
async function runTests() {
await testTyping();
await testTyping();
await testTyping();
await testTyping();
await testTyping();
summaries(`${APP_ROOT}/traces/reports`);
}
runTests();

View File

@ -0,0 +1,15 @@
{
"name": "ui-performance-infra",
"version": "1.0.0",
"description": "Tools to automate chrome performance profiling",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"puppeteer": "^12.0.1",
"tracelib": "^1.0.1"
}
}

136
app/client/perf/perf.js Normal file
View File

@ -0,0 +1,136 @@
const Tracelib = require("tracelib");
const puppeteer = require("puppeteer");
const fs = require("fs");
const {
delay,
login,
getFormattedTime,
sortObjectKeys,
} = require("./utils/utils");
module.exports = class Perf {
constructor(launchOptions = {}) {
this.launchOptions = {
...launchOptions,
};
this.traces = [];
this.traceInProgress = false;
this.browser = null;
}
/**
* Launches the browser and, gives you the page
*/
launch = async () => {
this.browser = await puppeteer.launch(this.launchOptions);
const pages_ = await this.browser.pages();
this.page = pages_[0];
await this._login();
};
_login = async () => {
await login(this.page);
await delay(2000, "after login");
};
startTrace = async (action = "foo") => {
if (this.traceInProgress) {
console.warn("Trace progress. You can run only one trace at a time");
return;
}
this.traceInProgress = true;
await delay(3000, `before starting trace ${action}`);
const path = `${APP_ROOT}/traces/${action}-${getFormattedTime()}-chrome-profile.json`;
await this.page.tracing.start({
path: path,
screenshots: true,
});
this.traces.push({ action, path });
};
stopTrace = async () => {
this.traceInProgress = false;
await delay(5000, "before stoping the trace");
await this.page.tracing.stop();
};
getPage = () => {
if (this.page) return this.page;
throw Error("Can't find the page, please call launch method.");
};
loadDSL = async (dsl) => {
const selector = ".createnew";
await this.page.waitForSelector(selector);
await this.page.click(selector);
// We goto the newly created app.
// Lets update the page
await this.page.waitForNavigation();
const currentUrl = this.page.url();
const pageIdRegex = /pages(.*)/;
const match = pageIdRegex.exec(currentUrl);
const pageId = match[1].split("/")[1];
await this.page.evaluate(
async ({ pageId, dsl }) => {
const layoutId = await fetch(`/api/v1/pages/${pageId}`)
.then((response) => response.json())
.then((data) => data.data.layouts[0].id);
const pageSaveUrl = "/api/v1/layouts/" + layoutId + "/pages/" + pageId;
await fetch(pageSaveUrl, {
headers: {
accept: "application/json, text/plain, */*",
"content-type": "application/json",
},
referrerPolicy: "strict-origin-when-cross-origin",
body: JSON.stringify(dsl),
method: "PUT",
mode: "cors",
credentials: "include",
})
.then((res) =>
console.log("Save page with new DSL response:", res.json()),
)
.catch((err) => {
console.log("Save page with new DSL error:", err);
});
},
{ pageId, dsl },
);
await this.page.goto(currentUrl.replace("generate-page", ""), {
waitUntil: "networkidle2",
timeout: 60000,
});
};
generateReport = async () => {
const report = {};
this.traces.forEach(({ path, action }) => {
report[action] = {};
const trace = require(path);
const tasks = new Tracelib.default(trace.traceEvents);
report[action].path = path;
report[action].summary = sortObjectKeys(tasks.getSummary());
report[action].warnings = sortObjectKeys(tasks.getWarningCounts());
});
fs.writeFile(
`${APP_ROOT}/traces/reports/${getFormattedTime()}.json`,
JSON.stringify(report, "", 4),
(err) => {
if (err) {
console.log("Error writing file", err);
} else {
console.log("Successfully wrote report");
}
},
);
};
close = async () => {
this.browser.close();
};
};

View File

@ -0,0 +1 @@
node index.js

View File

@ -0,0 +1,86 @@
const fs = require("fs");
const path = require("path");
exports.summaries = async (directory) => {
const files = await fs.promises.readdir(directory);
const results = {};
files.forEach((file) => {
if (file.endsWith(".json")) {
const content = require(`${APP_ROOT}/traces/reports/${file}`);
Object.keys(content).forEach((key) => {
if (!results[key]) {
results[key] = {};
}
if (!results[key]?.scripting) {
results[key].scripting = [];
}
results[key].scripting.push(content[key].summary.scripting);
if (!results[key]?.painting) {
results[key].painting = [];
}
results[key].painting.push(content[key].summary.painting);
if (!results[key]?.rendering) {
results[key].rendering = [];
}
results[key].rendering.push(content[key].summary.rendering);
});
}
});
generateReport(results);
};
const generateReport = (results) => {
var size = 5;
Object.keys(results).forEach((key) => {
const action = results[key];
Object.keys(action).forEach((key) => {
size = action[key].length;
const sum = action[key].reduce((sum, val) => sum + val, 0);
const avg = (sum / action[key].length).toFixed(2);
action[key].push(avg);
});
});
generateMarkdown(results, size);
};
const generateMarkdown = (results, size = 5) => {
let markdown = `<details><summary>Click to view performance test results</summary>\n\n| `;
for (let i = 0; i < size; i++) {
markdown = markdown + `| Run #${i + 1} `;
}
markdown = markdown + `| Avg `;
markdown += "|\n";
for (let i = 0; i <= size + 1; i++) {
markdown = markdown + `| ------------- `;
}
markdown += "|\n";
Object.keys(results).forEach((key) => {
const action = results[key];
markdown = markdown + key;
for (let i = 0; i <= size; i++) {
markdown = markdown + `| `;
}
markdown += "|\n";
Object.keys(action).forEach((key) => {
markdown += `| ${key} | `;
markdown += action[key].reduce((sum, val) => `${sum} | ${val} `);
markdown += "| \n";
});
});
markdown += "</details>";
fs.writeFile(`${APP_ROOT}/traces/reports/summary.md`, markdown, (err) => {
if (err) {
console.log("Error writing file", err);
} else {
console.log("Successfully wrote summary");
}
});
};

View File

@ -0,0 +1,143 @@
const fs = require("fs");
const path = require("path");
const delay = (time, msg = "") => {
console.log(`waiting ${msg}:`, time / 1000, "s");
return new Promise(function(resolve) {
setTimeout(resolve, time);
});
};
exports.delay = delay;
exports.getDevToolsPage = async (browser) => {
const targets = await browser.targets();
const devtoolsTarget = targets.filter((t) => {
return t.type() === "other" && t.url().startsWith("devtools://");
})[0];
// Hack to get a page pointing to the devtools
devtoolsTarget._targetInfo.type = "page";
const devtoolsPage = await devtoolsTarget.page();
return devtoolsPage;
};
exports.gotoProfiler = async (devtoolsPage) => {
await devtoolsPage.bringToFront();
await devtoolsPage.keyboard.down("MetaLeft");
await devtoolsPage.keyboard.press("[");
await devtoolsPage.keyboard.up("MetaLeft");
};
exports.getProfilerFrame = async (devtoolsPage) => {
const frames = await devtoolsPage.frames();
const reactProfiler = frames[2]; // This is not foolproof
return reactProfiler;
};
exports.startReactProfile = async (reactProfiler) => {
const recordButton =
"#container > div > div > div > div > div.Toolbar___30kHu > button.Button___1-PiG.InactiveRecordToggle___2CUtF";
await reactProfiler.waitForSelector(recordButton);
const container = await reactProfiler.$(recordButton);
console.log("Satring recording");
await reactProfiler.evaluate((el) => el.click(), container);
console.log("Recording started");
};
exports.stopReactProfile = async (reactProfiler) => {
const stopRecordingButton =
"#container > div > div > div > div > div.Toolbar___30kHu > button.Button___1-PiG.ActiveRecordToggle___1Cpcb";
await reactProfiler.waitForSelector(stopRecordingButton);
const container = await reactProfiler.$(stopRecordingButton);
console.log("Stopping recording");
await reactProfiler.evaluate((el) => el.click(), container);
console.log("Recording stopped");
};
exports.downloadReactProfile = async (reactProfiler) => {
const saveProfileButton =
"#container > div > div > div > div.LeftColumn___3I7-I > div.Toolbar___30kHu > button:nth-child(8)";
await reactProfiler.waitForSelector(saveProfileButton);
const container = await reactProfiler.$(saveProfileButton);
await reactProfiler.evaluate((el) => el.click(), container);
console.log("Downlaoded the profile");
};
exports.saveProfile = async (reactProfiler, name) => {
const anchorSelector =
"#container > div > div > div > div.LeftColumn___3I7-I > div.Toolbar___30kHu > a";
await reactProfiler.waitForSelector(anchorSelector);
const anchor = await reactProfiler.$(anchorSelector);
await reactProfiler.evaluate(
(el) => console.log(el.getAttribute("href")),
anchor,
);
const attr = await reactProfiler.$$eval(anchorSelector, (el) =>
el.map((x) => x.getAttribute("href")),
);
const url = attr[0];
const profile = await reactProfiler.evaluate(async (href) => {
const blob = await fetch(href).then(async (r) => r.blob());
const text = await blob.text();
return text;
}, url);
const location = path.join(__dirname, `/profiles/${name}.json`);
fs.writeFileSync(location, profile);
};
exports.login = async (page) => {
const url = "https://dev.appsmith.com/user/login";
await page.goto(url);
await page.setViewport({ width: 1440, height: 714 });
await delay(1000, "before login");
const emailSelector = "input[name='username']";
const passwordSelector = "input[name='password']";
const buttonSelector = "button[type='submit']";
try {
await page.waitForSelector(emailSelector);
await page.waitForSelector(passwordSelector);
await page.waitForSelector(buttonSelector);
} catch (e) {
console.error(e);
console.log(
"Screenshot:",
`${APP_ROOT}/traces/reports/login-selector-error.png`,
);
await page.screenshot({
path: `${APP_ROOT}/traces/reports/login-selector-error.png`,
});
}
await page.type(emailSelector, process.env.CYPRESS_TESTUSERNAME1);
await page.type(passwordSelector, process.env.CYPRESS_TESTPASSWORD1);
delay(1000, "before clicking login button");
await page.click(buttonSelector);
};
exports.getFormattedTime = () => {
var today = new Date();
var y = today.getFullYear();
var m = today.getMonth() + 1;
var d = today.getDate();
var h = today.getHours();
var mi = today.getMinutes();
var s = today.getSeconds();
return y + "-" + m + "-" + d + "-" + h + "-" + mi + "-" + s;
};
exports.sortObjectKeys = (obj) => {
const sortedObj = {};
Object.keys(obj)
.sort()
.forEach((key) => {
sortedObj[key] = obj[key];
});
return sortedObj;
};

402
app/client/perf/yarn.lock Normal file
View File

@ -0,0 +1,402 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@types/node@*":
version "17.0.0"
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.0.tgz#62797cee3b8b497f6547503b2312254d4fe3c2bb"
integrity sha512-eMhwJXc931Ihh4tkU+Y7GiLzT/y/DBNpNtr4yU9O2w3SYBsr9NaOPhQlLKRmoWtI54uNwuo0IOUFQjVOTZYRvw==
"@types/yauzl@^2.9.1":
version "2.9.2"
resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.9.2.tgz#c48e5d56aff1444409e39fa164b0b4d4552a7b7a"
integrity sha512-8uALY5LTvSuHgloDVUvWP3pIauILm+8/0pDMokuDYIoNsOkSwd5AiHBTSEJjKTDcZr5z8UpgOWZkxBF4iJftoA==
dependencies:
"@types/node" "*"
agent-base@6:
version "6.0.2"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==
dependencies:
debug "4"
balanced-match@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
base64-js@^1.3.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
bl@^4.0.3:
version "4.1.0"
resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a"
integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==
dependencies:
buffer "^5.5.0"
inherits "^2.0.4"
readable-stream "^3.4.0"
brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
dependencies:
balanced-match "^1.0.0"
concat-map "0.0.1"
buffer-crc32@~0.2.3:
version "0.2.13"
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=
buffer@^5.2.1, buffer@^5.5.0:
version "5.7.1"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0"
integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==
dependencies:
base64-js "^1.3.1"
ieee754 "^1.1.13"
chownr@^1.1.1:
version "1.1.4"
resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==
concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
debug@4, debug@^4.1.1:
version "4.3.3"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664"
integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==
dependencies:
ms "2.1.2"
debug@4.3.2:
version "4.3.2"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b"
integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==
dependencies:
ms "2.1.2"
devtools-protocol@0.0.937139:
version "0.0.937139"
resolved "https://registry.yarnpkg.com/devtools-protocol/-/devtools-protocol-0.0.937139.tgz#bdee3751fdfdb81cb701fd3afa94b1065dafafcf"
integrity sha512-daj+rzR3QSxsPRy5vjjthn58axO8c11j58uY0lG5vvlJk/EiOdCWOptGdkXDjtuRHr78emKq0udHCXM4trhoDQ==
end-of-stream@^1.1.0, end-of-stream@^1.4.1:
version "1.4.4"
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==
dependencies:
once "^1.4.0"
extract-zip@2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a"
integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==
dependencies:
debug "^4.1.1"
get-stream "^5.1.0"
yauzl "^2.10.0"
optionalDependencies:
"@types/yauzl" "^2.9.1"
fd-slicer@~1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e"
integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=
dependencies:
pend "~1.2.0"
find-up@^4.0.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19"
integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==
dependencies:
locate-path "^5.0.0"
path-exists "^4.0.0"
fs-constants@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
fs.realpath@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
get-stream@^5.1.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3"
integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==
dependencies:
pump "^3.0.0"
glob@^7.1.3:
version "7.2.0"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023"
integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
inherits "2"
minimatch "^3.0.4"
once "^1.3.0"
path-is-absolute "^1.0.0"
https-proxy-agent@5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2"
integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==
dependencies:
agent-base "6"
debug "4"
ieee754@^1.1.13:
version "1.2.1"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
inflight@^1.0.4:
version "1.0.6"
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=
dependencies:
once "^1.3.0"
wrappy "1"
inherits@2, inherits@^2.0.3, inherits@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
locate-path@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0"
integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==
dependencies:
p-locate "^4.1.0"
minimatch@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
dependencies:
brace-expansion "^1.1.7"
mkdirp-classic@^0.5.2:
version "0.5.3"
resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113"
integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==
ms@2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
node-fetch@2.6.5:
version "2.6.5"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.5.tgz#42735537d7f080a7e5f78b6c549b7146be1742fd"
integrity sha512-mmlIVHJEu5rnIxgEgez6b9GgWXbkZj5YZ7fx+2r94a2E+Uirsp6HsPTPlomfdHtpt/B0cdKviwkoaM6pyvUOpQ==
dependencies:
whatwg-url "^5.0.0"
once@^1.3.0, once@^1.3.1, once@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
dependencies:
wrappy "1"
p-limit@^2.2.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"
integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==
dependencies:
p-try "^2.0.0"
p-locate@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07"
integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==
dependencies:
p-limit "^2.2.0"
p-try@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
path-exists@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
path-is-absolute@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
pend@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA=
pkg-dir@4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3"
integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==
dependencies:
find-up "^4.0.0"
progress@2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
proxy-from-env@1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
pump@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64"
integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==
dependencies:
end-of-stream "^1.1.0"
once "^1.3.1"
puppeteer@^12.0.1:
version "12.0.1"
resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-12.0.1.tgz#ae79d0e174a07563e0bf2e05c94ccafce3e70033"
integrity sha512-YQ3GRiyZW0ddxTW+iiQcv2/8TT5c3+FcRUCg7F8q2gHqxd5akZN400VRXr9cHQKLWGukmJLDiE72MrcLK9tFHQ==
dependencies:
debug "4.3.2"
devtools-protocol "0.0.937139"
extract-zip "2.0.1"
https-proxy-agent "5.0.0"
node-fetch "2.6.5"
pkg-dir "4.2.0"
progress "2.0.3"
proxy-from-env "1.1.0"
rimraf "3.0.2"
tar-fs "2.1.1"
unbzip2-stream "1.4.3"
ws "8.2.3"
readable-stream@^3.1.1, readable-stream@^3.4.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==
dependencies:
inherits "^2.0.3"
string_decoder "^1.1.1"
util-deprecate "^1.0.1"
rimraf@3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
dependencies:
glob "^7.1.3"
safe-buffer@~5.2.0:
version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
string_decoder@^1.1.1:
version "1.3.0"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"
integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==
dependencies:
safe-buffer "~5.2.0"
tar-fs@2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784"
integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==
dependencies:
chownr "^1.1.1"
mkdirp-classic "^0.5.2"
pump "^3.0.0"
tar-stream "^2.1.4"
tar-stream@^2.1.4:
version "2.2.0"
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287"
integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==
dependencies:
bl "^4.0.3"
end-of-stream "^1.4.1"
fs-constants "^1.0.0"
inherits "^2.0.3"
readable-stream "^3.1.1"
through@^2.3.8:
version "2.3.8"
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
tr46@~0.0.3:
version "0.0.3"
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=
tracelib@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/tracelib/-/tracelib-1.0.1.tgz#bb44ea96c19b8d7a6c85a6ee1cac9945c5b75c64"
integrity sha512-T2Vkpa/7Vdm3sV8nXRn8vZ0tnq6wlnO4Zx7Pux+JA1W6DMlg5EtbNcPZu/L7XRTPc9S0eAKhEFR4p/u0GcsDpQ==
unbzip2-stream@1.4.3:
version "1.4.3"
resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz#b0da04c4371311df771cdc215e87f2130991ace7"
integrity sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==
dependencies:
buffer "^5.2.1"
through "^2.3.8"
util-deprecate@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
webidl-conversions@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=
whatwg-url@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"
integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0=
dependencies:
tr46 "~0.0.3"
webidl-conversions "^3.0.0"
wrappy@1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
ws@8.2.3:
version "8.2.3"
resolved "https://registry.yarnpkg.com/ws/-/ws-8.2.3.tgz#63a56456db1b04367d0b721a0b80cae6d8becbba"
integrity sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==
yauzl@^2.10.0:
version "2.10.0"
resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"
integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=
dependencies:
buffer-crc32 "~0.2.3"
fd-slicer "~1.1.0"