add abstraction for waitForSelector (#10844)

feature/default_network_editable
Brad Decker 4 years ago committed by GitHub
parent b65a94b7f3
commit 9079fb87ec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      package.json
  2. 392
      test/e2e/metamask-ui.spec.js
  3. 48
      test/e2e/webdriver/driver.js
  4. 18
      yarn.lock

@ -224,6 +224,7 @@
"copy-webpack-plugin": "^6.0.3",
"cross-spawn": "^7.0.3",
"css-loader": "^2.1.1",
"css-to-xpath": "^0.1.0",
"del": "^3.0.0",
"enzyme": "^3.10.0",
"enzyme-adapter-react-16": "^1.15.1",

@ -1,5 +1,5 @@
const assert = require('assert');
const { Key, until } = require('selenium-webdriver');
const { Key } = require('selenium-webdriver');
const enLocaleMessages = require('../../app/_locales/en/messages.json');
const { tinyDelayMs, regularDelayMs, largeDelayMs } = require('./helpers');
@ -143,10 +143,12 @@ describe('MetaMask', function () {
await driver.findVisibleElement('.qr-code__wrapper');
await driver.delay(regularDelayMs);
const accountModal = await driver.findElement('span .modal');
// wait for permission modal to be visible.
await driver.waitForSelector('span .modal');
await driver.clickElement('.account-modal__close');
await driver.wait(until.stalenessOf(accountModal));
// wait for account modal to be removed from DOM.
await driver.waitForSelector('span .modal', { state: 'detached' });
await driver.delay(regularDelayMs);
});
});
@ -242,10 +244,10 @@ describe('MetaMask', function () {
});
it('balance renders', async function () {
const balance = await driver.findElement(
'[data-testid="wallet-balance"] .list-item__heading',
);
await driver.wait(until.elementTextMatches(balance, /100\s*ETH/u));
await driver.waitForSelector({
css: '[data-testid="wallet-balance"] .list-item__heading',
text: '100 ETH',
});
await driver.delay(regularDelayMs);
});
});
@ -317,10 +319,10 @@ describe('MetaMask', function () {
return confirmedTxes.length === 1;
}, 10000);
const txValues = await driver.findElement(
'.transaction-list-item__primary-currency',
);
await driver.wait(until.elementTextMatches(txValues, /-1\s*ETH/u), 10000);
await driver.waitForSelector({
css: '.transaction-list-item__primary-currency',
text: '-1 ETH',
});
});
});
@ -355,17 +357,13 @@ describe('MetaMask', function () {
});
it('finds the transaction in the transactions list', async function () {
await driver.wait(async () => {
const confirmedTxes = await driver.findElements(
'.transaction-list__completed-transactions .transaction-list-item',
);
return confirmedTxes.length === 2;
}, 10000);
const txValues = await driver.findElement(
'.transaction-list-item__primary-currency',
await driver.waitForSelector(
'.transaction-list__completed-transactions .transaction-list-item:nth-child(2)',
);
await driver.wait(until.elementTextMatches(txValues, /-1\s*ETH/u), 10000);
await driver.waitForSelector({
css: '.transaction-list-item__primary-currency',
text: '-1 ETH',
});
});
});
@ -389,9 +387,12 @@ describe('MetaMask', function () {
await driver.clickElement('.advanced-gas-options-btn');
await driver.delay(regularDelayMs);
const gasModal = await driver.findElement('span .modal');
await driver.clickElement({ text: 'Save', tag: 'button' });
await driver.wait(until.stalenessOf(gasModal));
// Wait for gas modal to be removed from DOM
await driver.waitForSelector('span .modal', {
state: 'detached',
});
await driver.delay(regularDelayMs);
// Continue to next screen
@ -418,10 +419,13 @@ describe('MetaMask', function () {
return confirmedTxes.length === 3;
}, 10000);
const txValues = await driver.findElement(
'.transaction-list-item__primary-currency',
await driver.waitForSelector(
{
css: '.transaction-list-item__primary-currency',
text: '-1 ETH',
},
{ timeout: 10000 },
);
await driver.wait(until.elementTextMatches(txValues, /-1\s*ETH/u), 10000);
});
});
@ -542,10 +546,10 @@ describe('MetaMask', function () {
return confirmedTxes.length === 4;
}, 10000);
const txValue = await driver.findClickableElement(
'.transaction-list-item__primary-currency',
);
await driver.wait(until.elementTextMatches(txValue, /-3\s*ETH/u), 10000);
await driver.waitForSelector({
css: '.transaction-list-item__primary-currency',
text: '-3 ETH',
});
});
it('the transaction has the expected gas price', async function () {
@ -556,10 +560,10 @@ describe('MetaMask', function () {
const popoverCloseButton = await driver.findClickableElement(
'.popover-header__button',
);
const txGasPrice = await driver.findElement(
'[data-testid="transaction-breakdown__gas-price"]',
);
await driver.wait(until.elementTextMatches(txGasPrice, /^10$/u), 10000);
await driver.waitForSelector({
css: '[data-testid="transaction-breakdown__gas-price"]',
text: '10',
});
await popoverCloseButton.click();
});
});
@ -804,17 +808,16 @@ describe('MetaMask', function () {
await driver.clickElement({ text: 'Confirm', tag: 'button' });
await driver.delay(largeDelayMs);
await driver.wait(async () => {
const confirmedTxes = await driver.findElements(
'.transaction-list__completed-transactions .transaction-list-item',
);
return confirmedTxes.length === 6;
}, 10000);
await driver.waitForSelector(
'.transaction-list__completed-transactions .transaction-list-item:nth-of-type(6)',
);
const txAction = await driver.findElements('.list-item__heading');
await driver.wait(
until.elementTextMatches(txAction[0], /Contract\sDeployment/u),
10000,
await driver.waitForSelector(
{
css: '.list-item__title',
text: 'Contract Deployment',
},
{ timeout: 10000 },
);
await driver.delay(regularDelayMs);
});
@ -823,19 +826,23 @@ describe('MetaMask', function () {
await driver.switchToWindow(dapp);
await driver.delay(regularDelayMs);
let contractStatus = await driver.findElement('#contractStatus');
await driver.wait(
until.elementTextMatches(contractStatus, /Deployed/u),
15000,
await driver.waitForSelector(
{
css: '#contractStatus',
text: 'Deployed',
},
{ timeout: 15000 },
);
await driver.clickElement('#depositButton');
await driver.delay(largeDelayMs);
contractStatus = await driver.findElement('#contractStatus');
await driver.wait(
until.elementTextMatches(contractStatus, /Deposit\sinitiated/u),
10000,
await driver.waitForSelector(
{
css: '#contractStatus',
text: 'Deposit initiated',
},
{ timeout: 10000 },
);
await driver.switchToWindow(extension);
@ -845,19 +852,20 @@ describe('MetaMask', function () {
const txListValue = await driver.findClickableElement(
'.transaction-list-item__primary-currency',
);
await driver.wait(
until.elementTextMatches(txListValue, /-4\s*ETH/u),
10000,
await driver.waitForSelector(
{
css: '.transaction-list-item__primary-currency',
text: '-4 ETH',
},
{ timeout: 10000 },
);
await txListValue.click();
await driver.delay(regularDelayMs);
// Set the gas limit
await driver.clickElement('.confirm-detail-row__header-text--edit');
await driver.delay(regularDelayMs);
const gasModal = await driver.findElement('span .modal');
await driver.delay(regularDelayMs);
// wait for gas modal to be detached from DOM
await driver.waitForSelector('span .modal');
await driver.clickElement('.page-container__tab:nth-of-type(2)');
await driver.delay(regularDelayMs);
@ -879,26 +887,24 @@ describe('MetaMask', function () {
await driver.delay(1000);
await driver.clickElement({ text: 'Save', tag: 'button' });
await driver.delay(regularDelayMs);
await driver.wait(until.stalenessOf(gasModal));
// wait for gas modal to be detached from DOM
await driver.waitForSelector('span .modal', { state: 'detached' });
await driver.clickElement({ text: 'Confirm', tag: 'button' });
await driver.delay(regularDelayMs);
await driver.wait(async () => {
const confirmedTxes = await driver.findElements(
'.transaction-list__completed-transactions .transaction-list-item',
);
return confirmedTxes.length === 7;
}, 10000);
const txValues = await driver.findElements(
'.transaction-list-item__primary-currency',
await driver.waitForSelector(
'.transaction-list__completed-transactions .transaction-list-item:nth-of-type(7)',
{ timeout: 10000 },
);
await driver.wait(
until.elementTextMatches(txValues[0], /-4\s*ETH/u),
10000,
await driver.waitForSelector(
{
css:
'.transaction-list__completed-transactions .transaction-list-item__primary-currency',
text: '-4 ETH',
},
{ timeout: 10000 },
);
});
@ -927,23 +933,25 @@ describe('MetaMask', function () {
return confirmedTxes.length === 8;
}, 10000);
const txValues = await driver.findElement(
'.transaction-list-item__primary-currency',
await driver.waitForSelector(
{
css: '.transaction-list-item__primary-currency',
text: '-0 ETH',
},
{ timeout: 10000 },
);
await driver.wait(until.elementTextMatches(txValues, /-0\s*ETH/u), 10000);
await driver.closeAllWindowHandlesExcept([extension, dapp]);
await driver.switchToWindow(extension);
});
it('renders the correct ETH balance', async function () {
const balance = await driver.findElement(
'[data-testid="eth-overview__primary-currency"]',
);
await driver.delay(regularDelayMs);
await driver.wait(
until.elementTextMatches(balance, /^87.*\s*ETH.*$/u),
10000,
const balance = await driver.waitForSelector(
{
css: '[data-testid="eth-overview__primary-currency"]',
text: '87.',
},
{ timeout: 10000 },
);
const tokenAmount = await balance.getText();
assert.ok(/^87.*\s*ETH.*$/u.test(tokenAmount));
@ -989,8 +997,10 @@ describe('MetaMask', function () {
await driver.switchToWindow(dapp);
await driver.delay(tinyDelayMs);
const tokenContractAddress = await driver.findElement('#tokenAddress');
await driver.wait(until.elementTextMatches(tokenContractAddress, /0x/u));
const tokenContractAddress = await driver.waitForSelector({
css: '#tokenAddress',
text: '0x',
});
tokenAddress = await tokenContractAddress.getText();
await driver.delay(regularDelayMs);
@ -1025,18 +1035,15 @@ describe('MetaMask', function () {
});
it('renders the balance for the new token', async function () {
const balance = await driver.findElement(
'.wallet-overview .token-overview__primary-balance',
);
await driver.wait(until.elementTextMatches(balance, /^10\s*TST\s*$/u));
const tokenAmount = await balance.getText();
assert.ok(/^10\s*TST\s*$/u.test(tokenAmount));
await driver.waitForSelector({
css: '.wallet-overview .token-overview__primary-balance',
text: '10 TST',
});
await driver.delay(regularDelayMs);
});
});
describe('Send token from inside MetaMask', function () {
let gasModal;
it('starts to send a transaction', async function () {
await driver.clickElement('[data-testid="eth-overview-send"]');
await driver.delay(regularDelayMs);
@ -1048,24 +1055,20 @@ describe('MetaMask', function () {
const inputAmount = await driver.findElement('.unit-input__input');
await inputAmount.sendKeys('1');
});
// Set the gas limit
it('opens customize gas modal and saves options to continue', async function () {
await driver.clickElement('.advanced-gas-options-btn');
await driver.delay(regularDelayMs);
gasModal = await driver.findElement('span .modal');
await driver.delay(regularDelayMs);
});
it('opens customize gas modal', async function () {
// Wait for gas modal to be visible
await driver.waitForSelector('span .modal');
await driver.findElement('.page-container__title');
await driver.clickElement({ text: 'Save', tag: 'button' });
await driver.delay(regularDelayMs);
// wait for gas modal to be removed from DOM.
await driver.waitForSelector('span .modal', { state: 'detached' });
});
it('transitions to the confirm screen', async function () {
await driver.wait(until.stalenessOf(gasModal));
// Continue to next screen
await driver.clickElement({ text: 'Next', tag: 'button' });
await driver.delay(regularDelayMs);
@ -1116,25 +1119,22 @@ describe('MetaMask', function () {
return confirmedTxes.length === 1;
}, 10000);
const txValues = await driver.findElements(
'.transaction-list-item__primary-currency',
);
assert.equal(txValues.length, 1);
await driver.wait(
until.elementTextMatches(txValues[0], /-1\s*TST/u),
10000,
await driver.waitForSelector(
{
css: '.transaction-list-item__primary-currency',
text: '-1 TST',
},
{ timeout: 10000 },
);
const txStatuses = await driver.findElements('.list-item__heading');
await driver.wait(
until.elementTextMatches(txStatuses[0], /Send\sTST/u),
10000,
);
await driver.waitForSelector({
css: '.list-item__heading',
text: 'Send TST',
});
});
});
describe('Send a custom token from dapp', function () {
let gasModal;
it('sends an already created token', async function () {
const windowHandles = await driver.getAllWindowHandles();
const extension = windowHandles[0];
@ -1153,14 +1153,14 @@ describe('MetaMask', function () {
await driver.delay(largeDelayMs);
await driver.findElements('.transaction-list__pending-transactions');
const txListValue = await driver.findClickableElement(
'.transaction-list-item__primary-currency',
await driver.waitForSelector(
{
css: '.transaction-list-item__primary-currency',
text: '-1.5 TST',
},
{ timeout: 10000 },
);
await driver.wait(
until.elementTextMatches(txListValue, /-1.5\s*TST/u),
10000,
);
await txListValue.click();
await driver.clickElement('.transaction-list-item__primary-currency');
await driver.delay(regularDelayMs);
const transactionAmounts = await driver.findElements(
@ -1173,7 +1173,8 @@ describe('MetaMask', function () {
await driver.clickElement('.confirm-detail-row__header-text--edit');
await driver.delay(regularDelayMs);
gasModal = await driver.findElement('span .modal');
// wait for gas modal to be visible
await driver.waitForSelector('span .modal');
});
it('customizes gas', async function () {
@ -1196,7 +1197,11 @@ describe('MetaMask', function () {
await driver.delay(1000);
await driver.clickElement('.page-container__footer-button');
await driver.wait(until.stalenessOf(gasModal));
// Wait for gas modal to be removed from DOM.
await driver.waitForSelector('span .modal', {
state: 'detached',
});
const gasFeeInputs = await driver.findElements(
'.confirm-detail-row__primary',
@ -1224,28 +1229,24 @@ describe('MetaMask', function () {
return confirmedTxes.length === 2;
}, 10000);
const txValues = await driver.findElements(
'.transaction-list-item__primary-currency',
);
await driver.wait(until.elementTextMatches(txValues[0], /-1.5\s*TST/u));
const txStatuses = await driver.findElements('.list-item__heading');
await driver.wait(
until.elementTextMatches(txStatuses[0], /Send\sTST/u),
10000,
);
await driver.waitForSelector({
css: '.transaction-list-item__primary-currency',
text: '-1.5 TST',
});
const tokenBalanceAmount = await driver.findElements(
'.token-overview__primary-balance',
);
await driver.wait(
until.elementTextMatches(tokenBalanceAmount[0], /7.5\s*TST/u),
10000,
);
await driver.waitForSelector({
css: '.list-item__heading',
text: 'Send TST',
});
await driver.waitForSelector({
css: '.token-overview__primary-balance',
text: '7.5 TST',
});
});
});
describe('Approves a custom token from dapp', function () {
let gasModal;
it('approves an already created token', async function () {
const windowHandles = await driver.getAllWindowHandles();
const extension = windowHandles[0];
@ -1271,12 +1272,13 @@ describe('MetaMask', function () {
return pendingTxes.length === 1;
}, 10000);
const [txtListHeading] = await driver.findElements(
'.transaction-list-item .list-item__heading',
);
await driver.wait(
until.elementTextMatches(txtListHeading, /Approve TST spend limit/u),
);
await driver.waitForSelector({
// Selects only the very first transaction list item immediately following the 'Pending' header
css:
'.transaction-list__pending-transactions .transaction-list__header + .transaction-list-item .list-item__heading',
text: 'Approve TST spend limit',
});
await driver.clickElement('.transaction-list-item');
await driver.delay(regularDelayMs);
});
@ -1310,7 +1312,8 @@ describe('MetaMask', function () {
);
await driver.delay(regularDelayMs);
gasModal = await driver.findElement('span .modal');
// Wait for the gas modal to be visible
await driver.waitForSelector('span .modal');
});
it('customizes gas', async function () {
@ -1333,7 +1336,11 @@ describe('MetaMask', function () {
await driver.delay(1000);
await driver.clickElement('.page-container__footer-button');
await driver.wait(until.stalenessOf(gasModal));
// wait for the gas modal to be removed from DOM.
await driver.waitForSelector('span .modal', {
state: 'detached',
});
const gasFeeInEth = await driver.findElement(
'.confirm-approve-content__transaction-details-content__secondary-fee',
@ -1346,10 +1353,9 @@ describe('MetaMask', function () {
'.confirm-approve-content__small-blue-text.cursor-pointer',
);
await editButtons[1].click();
await driver.delay(regularDelayMs);
const permissionModal = await driver.findElement('span .modal');
// wait for permission modal to be visible
await driver.waitForSelector('span .modal');
const radioButtons = await driver.findClickableElements(
'.edit-approval-permission__edit-section__radio-button',
);
@ -1361,9 +1367,9 @@ describe('MetaMask', function () {
await driver.delay(regularDelayMs);
await driver.clickElement({ text: 'Save', tag: 'button' });
await driver.delay(regularDelayMs);
await driver.wait(until.stalenessOf(permissionModal));
// wait for permission modal to be removed from DOM.
await driver.waitForSelector('span .modal', { state: 'detached' });
const permissionInfo = await driver.findElements(
'.confirm-approve-content__medium-text',
@ -1385,10 +1391,12 @@ describe('MetaMask', function () {
return confirmedTxes.length === 3;
}, 10000);
const txStatuses = await driver.findElements('.list-item__heading');
await driver.wait(
until.elementTextMatches(txStatuses[0], /Approve TST spend limit/u),
);
await driver.waitForSelector({
// Select only the heading of the first entry in the transaction list.
css:
'.transaction-list__completed-transactions .transaction-list-item:first-child .list-item__heading',
text: 'Approve TST spend limit',
});
});
});
@ -1420,10 +1428,10 @@ describe('MetaMask', function () {
return pendingTxes.length === 1;
}, 10000);
const [txListValue] = await driver.findElements(
'.transaction-list-item__primary-currency',
);
await driver.wait(until.elementTextMatches(txListValue, /-1.5\s*TST/u));
await driver.waitForSelector({
css: '.transaction-list-item__primary-currency',
text: '-1.5 TST',
});
await driver.clickElement('.transaction-list-item');
await driver.delay(regularDelayMs);
});
@ -1442,12 +1450,18 @@ describe('MetaMask', function () {
return confirmedTxes.length === 4;
}, 10000);
const txValues = await driver.findElements(
'.transaction-list-item__primary-currency',
);
await driver.wait(until.elementTextMatches(txValues[0], /-1.5\s*TST/u));
const txStatuses = await driver.findElements('.list-item__heading');
await driver.wait(until.elementTextMatches(txStatuses[0], /Send TST/u));
await driver.waitForSelector({
// Select the heading of the first transaction list item in the
// completed transaction list with text matching Send TST
css:
'.transaction-list__completed-transactions .transaction-list-item:first-child .list-item__heading',
text: 'Send TST',
});
await driver.waitForSelector({
css: '.transaction-list-item__primary-currency',
text: '-1.5 TST',
});
});
});
@ -1480,12 +1494,13 @@ describe('MetaMask', function () {
return pendingTxes.length === 1;
}, 10000);
const [txtListHeading] = await driver.findElements(
'.transaction-list-item .list-item__heading',
);
await driver.wait(
until.elementTextMatches(txtListHeading, /Approve TST spend limit/u),
);
await driver.waitForSelector({
// Selects only the very first transaction list item immediately following the 'Pending' header
css:
'.transaction-list__pending-transactions .transaction-list__header + .transaction-list-item .list-item__heading',
text: 'Approve TST spend limit',
});
await driver.clickElement('.transaction-list-item');
await driver.delay(regularDelayMs);
});
@ -1517,10 +1532,11 @@ describe('MetaMask', function () {
return confirmedTxes.length === 5;
}, 10000);
const txStatuses = await driver.findElements('.list-item__heading');
await driver.wait(
until.elementTextMatches(txStatuses[0], /Approve TST spend limit/u),
);
await driver.waitForSelector({
css:
'.transaction-list__completed-transactions .transaction-list-item:first-child .list-item__heading',
text: 'Approve TST spend limit',
});
});
});
@ -1530,13 +1546,15 @@ describe('MetaMask', function () {
await driver.clickElement('[data-testid="token-options__hide"]');
const confirmHideModal = await driver.findElement('span .modal');
// wait for confirm hide modal to be visible
await driver.waitForSelector('span .modal');
await driver.clickElement(
'[data-testid="hide-token-confirmation__hide"]',
);
await driver.wait(until.stalenessOf(confirmHideModal));
// wait for confirm hide modal to be removed from DOM.
await driver.waitForSelector('span .modal', { state: 'detached' });
});
});
@ -1562,10 +1580,10 @@ describe('MetaMask', function () {
});
it('renders the balance for the chosen token', async function () {
const balance = await driver.findElement(
'.token-overview__primary-balance',
);
await driver.wait(until.elementTextMatches(balance, /0\s*BAT/u));
await driver.waitForSelector({
css: '.token-overview__primary-balance',
text: '0 BAT',
});
await driver.delay(regularDelayMs);
});
});
@ -1658,13 +1676,15 @@ describe('MetaMask', function () {
await driver.clickElement('.btn-danger');
await driver.delay(regularDelayMs);
const confirmDeleteNetworkModal = await driver.findElement('span .modal');
// wait for confirm delete modal to be visible.
await driver.waitForSelector('span .modal');
await driver.clickElement(
'.button.btn-danger.modal-container__footer-button',
);
await driver.wait(until.stalenessOf(confirmDeleteNetworkModal));
// wait for confirm delete modal to be removed from DOM.
await driver.waitForSelector('span .modal', { state: 'detached' });
const newNetworkListItems = await driver.findElements(
'.networks-tab__networks-list-name',

@ -1,6 +1,7 @@
const { promises: fs } = require('fs');
const { strict: assert } = require('assert');
const { until, error: webdriverError, By } = require('selenium-webdriver');
const cssToXPath = require('css-to-xpath');
class Driver {
/**
@ -28,8 +29,31 @@ class Driver {
// xpath locator.
return By.xpath(locator.xpath);
} else if (locator.text) {
// Providing a text prop, and optionally a tag, will use xpath to look
// for an element with the tag that has matching text.
// Providing a text prop, and optionally a tag or css prop, will use
// xpath to look for an element with the tag that has matching text.
if (locator.css) {
// When providing css prop we use cssToXPath to build a xpath string
// We provide two cases to check for, first a text node of the
// element that matches the text provided OR we test the stringified
// contents of the element in the case where text is split across
// multiple children. In the later case non literal spaces are stripped
// so we do the same with the input to provide a consistent API.
const xpath = cssToXPath
.parse(locator.css)
.where(
cssToXPath.xPathBuilder
.string()
.contains(locator.text)
.or(
cssToXPath.xPathBuilder
.string()
.contains(locator.text.split(' ').join('')),
),
)
.toXPath();
return By.xpath(xpath);
}
// The tag prop is optional and further refines which elements match
return By.xpath(
`//${locator.tag ?? '*'}[contains(text(), '${locator.text}')]`,
);
@ -47,6 +71,26 @@ class Driver {
await this.driver.wait(condition, timeout);
}
async waitForSelector(
rawLocator,
{ timeout = this.timeout, state = 'visible' } = {},
) {
// Playwright has a waitForSelector method that will become a shallow
// replacement for the implementation below. It takes an option options
// bucket that can include the state attribute to wait for elements that
// match the selector to be removed from the DOM.
const selector = this.buildLocator(rawLocator);
if (state === 'visible') {
return await this.driver.wait(until.elementLocated(selector), timeout);
} else if (state === 'detached') {
return await this.driver.wait(
until.stalenessOf(await this.findElement(selector)),
timeout,
);
}
throw new Error(`Provided state selector ${state} is not supported`);
}
async quit() {
await this.driver.quit();
}

@ -5898,6 +5898,11 @@ bn.js@^5.1.1, bn.js@^5.1.2:
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.1.3.tgz#beca005408f642ebebea80b042b4d18d2ac0ee6b"
integrity sha512-GkTiFpjFtUzU9CbMeJ5iazkCzGL3jrhzerzZIuqLABjbwRaFt33I9tUdSNryIptM+RxDet6OKm2WnLXzW51KsQ==
bo-selector@0.0.10:
version "0.0.10"
resolved "https://registry.yarnpkg.com/bo-selector/-/bo-selector-0.0.10.tgz#9816dcb00adf374ea87941a863b2acfc026afa3e"
integrity sha1-mBbcsArfN06oeUGoY7Ks/AJq+j4=
body-parser@1.19.0, body-parser@^1.16.0:
version "1.19.0"
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a"
@ -7794,6 +7799,14 @@ css-select@^1.1.0, css-select@~1.2.0:
domutils "1.5.1"
nth-check "~1.0.1"
css-to-xpath@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/css-to-xpath/-/css-to-xpath-0.1.0.tgz#ac0d1c26cef023f7bd8cf2e1fc1f77134bc70c47"
integrity sha1-rA0cJs7wI/e9jPLh/B93E0vHDEc=
dependencies:
bo-selector "0.0.10"
xpath-builder "0.0.7"
css-vendor@^2.0.8:
version "2.0.8"
resolved "https://registry.yarnpkg.com/css-vendor/-/css-vendor-2.0.8.tgz#e47f91d3bd3117d49180a3c935e62e3d9f7f449d"
@ -25703,6 +25716,11 @@ xor-distance@^2.0.0:
resolved "https://registry.yarnpkg.com/xor-distance/-/xor-distance-2.0.0.tgz#cad3920d3a1e3d73eeedc61a554e51972dae0798"
integrity sha512-AsAqZfPAuWx7qB/0kyRDUEvoU3QKsHWzHU9smFlkaiprEpGfJ/NBbLze2Uq0rdkxCxkNM9uOLvz/KoNBCbZiLQ==
xpath-builder@0.0.7:
version "0.0.7"
resolved "https://registry.yarnpkg.com/xpath-builder/-/xpath-builder-0.0.7.tgz#67d6bbc3f6a320ec317e3e6368c5706b6111deec"
integrity sha1-Z9a7w/ajIOwxfj5jaMVwa2ER3uw=
xregexp@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-2.0.0.tgz#52a63e56ca0b84a7f3a5f3d61872f126ad7a5943"

Loading…
Cancel
Save