Fix E2E tests. Add Playwright (#698)
This commit is contained in:
parent
914e969083
commit
2fd6305944
|
@ -126,58 +126,22 @@ jobs:
|
|||
with:
|
||||
name: screenshots
|
||||
path: failed-*
|
||||
accessibility:
|
||||
name: "Accessibility | Hosted"
|
||||
needs: [lint, format, compile, unittest]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js 14.x
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 14.x
|
||||
- name: Accessibility Check
|
||||
run: |
|
||||
# Ubuntu gets mad when webpack runs too many files watchers
|
||||
cat /proc/sys/fs/inotify/max_user_watches
|
||||
sudo sysctl fs.inotify.max_user_watches=524288
|
||||
sudo sysctl -p
|
||||
npm ci
|
||||
npm start &
|
||||
npx wait-on -i 5000 https-get://0.0.0.0:1234/
|
||||
node utils/accesibilityCheck.js
|
||||
shell: bash
|
||||
env:
|
||||
NODE_TLS_REJECT_UNAUTHORIZED: 0
|
||||
endtoendhosted:
|
||||
name: "End to End Tests"
|
||||
endtoend:
|
||||
name: "E2E"
|
||||
needs: [cleanupaccounts]
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
NODE_TLS_REJECT_UNAUTHORIZED: 0
|
||||
PORTAL_RUNNER_SUBSCRIPTION: ${{ secrets.PORTAL_RUNNER_SUBSCRIPTION }}
|
||||
PORTAL_RUNNER_RESOURCE_GROUP: ${{ secrets.PORTAL_RUNNER_RESOURCE_GROUP }}
|
||||
PORTAL_RUNNER_DATABASE_ACCOUNT: ${{ secrets.PORTAL_RUNNER_DATABASE_ACCOUNT }}
|
||||
PORTAL_RUNNER_DATABASE_ACCOUNT_KEY: ${{ secrets.PORTAL_RUNNER_DATABASE_ACCOUNT_KEY }}
|
||||
PORTAL_RUNNER_MONGO_DATABASE_ACCOUNT: ${{ secrets.PORTAL_RUNNER_MONGO_DATABASE_ACCOUNT }}
|
||||
PORTAL_RUNNER_MONGO_DATABASE_ACCOUNT_KEY: ${{ secrets.PORTAL_RUNNER_MONGO_DATABASE_ACCOUNT_KEY }}
|
||||
NOTEBOOKS_TEST_RUNNER_TENANT_ID: ${{ secrets.NOTEBOOKS_TEST_RUNNER_TENANT_ID }}
|
||||
NOTEBOOKS_TEST_RUNNER_CLIENT_ID: ${{ secrets.NOTEBOOKS_TEST_RUNNER_CLIENT_ID }}
|
||||
NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET: ${{ secrets.NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET }}
|
||||
PORTAL_RUNNER_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_SQL }}
|
||||
MONGO_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_MONGO }}
|
||||
CASSANDRA_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_CASSANDRA }}
|
||||
TABLES_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_TABLE }}
|
||||
DATA_EXPLORER_ENDPOINT: "https://localhost:1234/hostedExplorer.html"
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
test-file:
|
||||
- ./test/cassandra/container.spec.ts
|
||||
- ./test/mongo/mongoIndexPolicy.spec.ts
|
||||
- ./test/notebooks/uploadAndOpenNotebook.spec.ts
|
||||
- ./test/selfServe/selfServeExample.spec.ts
|
||||
- ./test/sql/container.spec.ts
|
||||
- ./test/mongo/container.spec.ts
|
||||
- ./test/selfServe/selfServeExample.spec.ts
|
||||
- ./test/notebooks/upload.spec.ts
|
||||
- ./test/sql/resourceToken.spec.ts
|
||||
- ./test/tables/container.spec.ts
|
||||
steps:
|
||||
|
@ -188,16 +152,15 @@ jobs:
|
|||
node-version: 14.x
|
||||
- run: npm ci
|
||||
- run: npm start &
|
||||
- run: node utils/cleanupDBs.js
|
||||
- run: npm run wait-for-server
|
||||
- name: ${{ matrix['test-file'] }}
|
||||
run: npx jest -c ./jest.config.e2e.js --detectOpenHandles ${{ matrix['test-file'] }}
|
||||
run: npx jest -c ./jest.config.playwright.js ${{ matrix['test-file'] }}
|
||||
shell: bash
|
||||
- uses: actions/upload-artifact@v2
|
||||
if: failure()
|
||||
with:
|
||||
name: screenshots
|
||||
path: failed-*
|
||||
path: screenshots/
|
||||
cleanupaccounts:
|
||||
name: "Cleanup Test Database Accounts"
|
||||
runs-on: ubuntu-latest
|
||||
|
|
|
@ -14,4 +14,6 @@ Contracts/*
|
|||
.DS_Store
|
||||
.cache/
|
||||
.env
|
||||
failure.png
|
||||
failure.png
|
||||
screenshots/*
|
||||
GettingStarted-ignore*.ipynb
|
|
@ -153,7 +153,7 @@ Cosmos Explorer has been under constant development for over 5 years. As a resul
|
|||
|
||||
✅ DO
|
||||
|
||||
- Use [Puppeteer](https://developers.google.com/web/tools/puppeteer) and [Jest](https://jestjs.io/)
|
||||
- Use [Playwright](https://github.com/microsoft/playwright) and [Jest](https://jestjs.io/)
|
||||
- Write or modify an existing E2E test that covers the primary use case of any major feature.
|
||||
- Use caution. Do not try to cover every case. End to End tests can be slow and brittle.
|
||||
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
const isCI = require("is-ci");
|
||||
|
||||
module.exports = {
|
||||
exitOnPageError: false,
|
||||
launchOptions: {
|
||||
headless: isCI,
|
||||
slowMo: 10,
|
||||
timeout: 60000,
|
||||
},
|
||||
contextOptions: {
|
||||
ignoreHTTPSErrors: true,
|
||||
viewport: {
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
},
|
||||
},
|
||||
};
|
|
@ -1,12 +0,0 @@
|
|||
const isCI = require("is-ci");
|
||||
|
||||
module.exports = {
|
||||
launch: {
|
||||
headless: isCI,
|
||||
slowMo: 55,
|
||||
defaultViewport: null,
|
||||
ignoreHTTPSErrors: true,
|
||||
args: ["--disable-web-security"],
|
||||
exitOnPageError: false,
|
||||
},
|
||||
};
|
|
@ -1,5 +0,0 @@
|
|||
module.exports = {
|
||||
preset: "jest-puppeteer",
|
||||
testMatch: ["<rootDir>/test/**/*.spec.[jt]s?(x)"],
|
||||
setupFiles: ["dotenv/config"],
|
||||
};
|
|
@ -0,0 +1,7 @@
|
|||
module.exports = {
|
||||
preset: "jest-playwright-preset",
|
||||
testMatch: ["<rootDir>/test/**/*.spec.[jt]s?(x)"],
|
||||
setupFiles: ["dotenv/config"],
|
||||
testEnvironment: "./test/playwrightEnv.js",
|
||||
setupFilesAfterEnv: ["expect-playwright"],
|
||||
};
|
File diff suppressed because it is too large
Load Diff
|
@ -111,15 +111,12 @@
|
|||
"@types/d3": "5.9.2",
|
||||
"@types/enzyme": "3.10.7",
|
||||
"@types/enzyme-adapter-react-16": "1.0.6",
|
||||
"@types/expect-puppeteer": "4.4.5",
|
||||
"@types/hasher": "0.0.31",
|
||||
"@types/jest": "26.0.20",
|
||||
"@types/jest-environment-puppeteer": "4.4.1",
|
||||
"@types/memoize-one": "4.1.1",
|
||||
"@types/node": "12.11.1",
|
||||
"@types/promise.prototype.finally": "2.0.3",
|
||||
"@types/prop-types": "15.5.8",
|
||||
"@types/puppeteer": "5.4.3",
|
||||
"@types/q": "1.5.1",
|
||||
"@types/react": "17.0.3",
|
||||
"@types/react-dom": "17.0.3",
|
||||
|
@ -131,7 +128,6 @@
|
|||
"@types/underscore": "1.7.36",
|
||||
"@typescript-eslint/eslint-plugin": "4.0.1",
|
||||
"@typescript-eslint/parser": "4.0.1",
|
||||
"axe-puppeteer": "1.1.0",
|
||||
"babel-jest": "24.9.0",
|
||||
"babel-loader": "8.1.0",
|
||||
"buffer": "5.1.0",
|
||||
|
@ -146,6 +142,7 @@
|
|||
"eslint-plugin-no-null": "1.0.2",
|
||||
"eslint-plugin-prefer-arrow": "1.2.2",
|
||||
"eslint-plugin-react-hooks": "4.2.0",
|
||||
"expect-playwright": "0.3.3",
|
||||
"expose-loader": "0.7.5",
|
||||
"fast-glob": "3.2.5",
|
||||
"file-loader": "2.0.0",
|
||||
|
@ -155,7 +152,7 @@
|
|||
"html-webpack-plugin": "3.2.0",
|
||||
"jest": "25.5.4",
|
||||
"jest-canvas-mock": "2.1.0",
|
||||
"jest-puppeteer": "4.4.0",
|
||||
"jest-playwright-preset": "1.5.1",
|
||||
"jest-trx-results-processor": "0.0.7",
|
||||
"less": "3.8.1",
|
||||
"less-loader": "4.1.0",
|
||||
|
@ -163,8 +160,8 @@
|
|||
"mini-css-extract-plugin": "0.4.3",
|
||||
"monaco-editor-webpack-plugin": "1.7.0",
|
||||
"node-fetch": "2.6.1",
|
||||
"playwright": "1.10.0",
|
||||
"prettier": "2.2.1",
|
||||
"puppeteer": "8.0.0",
|
||||
"raw-loader": "0.5.1",
|
||||
"rimraf": "3.0.0",
|
||||
"sinon": "3.2.1",
|
||||
|
|
|
@ -1,148 +1,35 @@
|
|||
import "expect-puppeteer";
|
||||
import { Frame } from "puppeteer";
|
||||
import { generateUniqueName, login } from "../utils/shared";
|
||||
import { jest } from "@jest/globals";
|
||||
import "expect-playwright";
|
||||
import { safeClick } from "../utils/safeClick";
|
||||
import { generateUniqueName } from "../utils/shared";
|
||||
jest.setTimeout(120000);
|
||||
|
||||
jest.setTimeout(300000);
|
||||
const RENDER_DELAY = 800;
|
||||
const RETRY_DELAY = 5000;
|
||||
const CREATE_DELAY = 10000;
|
||||
const LOADING_STATE_DELAY = 2500;
|
||||
test("Cassandra keyspace and table CRUD", async () => {
|
||||
const keyspaceId = generateUniqueName("keyspace");
|
||||
const tableId = generateUniqueName("table");
|
||||
|
||||
describe("Collection Add and Delete Cassandra spec", () => {
|
||||
it("creates a collection", async () => {
|
||||
try {
|
||||
const keyspaceId = generateUniqueName("key");
|
||||
const tableId = generateUniqueName("tab");
|
||||
const frame = await login(process.env.CASSANDRA_CONNECTION_STRING);
|
||||
|
||||
// create new table
|
||||
await frame.waitFor('button[data-test="New Table"]', { visible: true });
|
||||
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
|
||||
await frame.click('button[data-test="New Table"]');
|
||||
|
||||
// type keyspace id
|
||||
await frame.waitFor('input[id="keyspace-id"]', { visible: true });
|
||||
await frame.type('input[id="keyspace-id"]', keyspaceId);
|
||||
|
||||
// type table id
|
||||
await frame.waitFor('input[class="textfontclr"]');
|
||||
await frame.type('input[class="textfontclr"]', tableId);
|
||||
|
||||
// click submit
|
||||
await frame.waitFor("#cassandraaddcollectionpane > div > form > div.paneFooter > div > input");
|
||||
await frame.click("#cassandraaddcollectionpane > div > form > div.paneFooter > div > input");
|
||||
|
||||
// open database menu
|
||||
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
|
||||
await frame.waitFor(LOADING_STATE_DELAY);
|
||||
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
|
||||
|
||||
const databases = await frame.$$(`div[class="databaseHeader main1 nodeItem "] > div[class="treeNodeHeader "]`);
|
||||
const selectedDbId = await frame.evaluate((element) => {
|
||||
return element.attributes["data-test"].textContent;
|
||||
}, databases[0]);
|
||||
|
||||
await frame.waitFor(`div[data-test="${selectedDbId}"]`), { visible: true };
|
||||
await frame.waitFor(CREATE_DELAY);
|
||||
await frame.waitFor("div[class='rowData'] > span[class='message']");
|
||||
|
||||
const didCreateContainer = await frame.$$eval("div[class='rowData'] > span[class='message']", (elements) => {
|
||||
return elements.some((el) => el.textContent.includes("Successfully created"));
|
||||
});
|
||||
|
||||
expect(didCreateContainer).toBe(true);
|
||||
|
||||
await frame.waitFor(`div[data-test="${selectedDbId}"]`), { visible: true };
|
||||
await frame.waitFor(LOADING_STATE_DELAY);
|
||||
|
||||
await clickDBMenu(selectedDbId, frame);
|
||||
|
||||
const collections = await frame.$$(
|
||||
`div[class="collectionHeader main2 nodeItem "] > div[class="treeNodeHeader "]`
|
||||
);
|
||||
|
||||
if (collections.length) {
|
||||
await frame.waitFor(`div[class="collectionHeader main2 nodeItem "] > div[class="treeNodeHeader "]`, {
|
||||
visible: true,
|
||||
});
|
||||
|
||||
const textId = await frame.evaluate((element) => {
|
||||
return element.attributes["data-test"].textContent;
|
||||
}, collections[0]);
|
||||
await frame.waitFor(`div[data-test="${textId}"]`, { visible: true });
|
||||
// delete container
|
||||
|
||||
// click context menu for container
|
||||
await frame.waitFor(`div[data-test="${textId}"] > div > button`, { visible: true });
|
||||
await frame.click(`div[data-test="${textId}"] > div > button`);
|
||||
|
||||
// click delete container
|
||||
await frame.waitFor(RENDER_DELAY);
|
||||
await frame.waitFor('span[class="treeComponentMenuItemLabel deleteCollectionMenuItemLabel"]');
|
||||
await frame.click('span[class="treeComponentMenuItemLabel deleteCollectionMenuItemLabel"]');
|
||||
|
||||
// confirm delete container
|
||||
await frame.waitFor('input[id="confirmCollectionId"]', { visible: true });
|
||||
await frame.type('input[id="confirmCollectionId"]', textId);
|
||||
|
||||
// click delete
|
||||
await frame.waitFor('button[id="sidePanelOkButton"]', { visible: true });
|
||||
await frame.click('button[id="sidePanelOkButton"]');
|
||||
await frame.waitFor(LOADING_STATE_DELAY);
|
||||
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
|
||||
|
||||
await expect(page).not.toMatchElement(`div[data-test="${textId}"]`);
|
||||
}
|
||||
|
||||
// click context menu for database
|
||||
await frame.waitFor(`div[data-test="${keyspaceId}"] > div > button`);
|
||||
await frame.waitFor(RENDER_DELAY);
|
||||
const button = await frame.$(`div[data-test="${keyspaceId}"] > div > button`);
|
||||
await button.focus();
|
||||
await button.asElement().click();
|
||||
|
||||
// click delete database
|
||||
await frame.waitFor(RENDER_DELAY);
|
||||
const dbElements = await frame.$$('span[class="treeComponentMenuItemLabel deleteDatabaseMenuItemLabel"]');
|
||||
await dbElements[0].click();
|
||||
|
||||
// confirm delete database
|
||||
await frame.waitForSelector('input[id="confirmDatabaseId"]', { visible: true });
|
||||
await frame.waitFor(RENDER_DELAY);
|
||||
await frame.type('input[id="confirmDatabaseId"]', keyspaceId.trim());
|
||||
|
||||
// click delete
|
||||
await frame.click('button[id="sidePanelOkButton"]');
|
||||
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
|
||||
await frame.waitFor(LOADING_STATE_DELAY);
|
||||
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
|
||||
await expect(page).not.toMatchElement(`div[data-test="${keyspaceId}"]`);
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const testName = (expect as any).getState().currentTestName;
|
||||
await page.screenshot({ path: `failed-${testName}.jpg` });
|
||||
throw error;
|
||||
}
|
||||
await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-cassandra-runner");
|
||||
await page.waitForSelector("iframe");
|
||||
const explorer = page.frame({
|
||||
name: "explorer",
|
||||
});
|
||||
|
||||
await explorer.click('[data-test="New Table"]');
|
||||
await explorer.click('[data-test="addCollection-keyspaceId"]');
|
||||
await explorer.fill('[data-test="addCollection-keyspaceId"]', keyspaceId);
|
||||
await explorer.click('[data-test="addCollection-tableId"]');
|
||||
await explorer.fill('[data-test="addCollection-tableId"]', tableId);
|
||||
await explorer.click('[aria-label="Add Table"] [data-test="addCollection-createCollection"]');
|
||||
await safeClick(explorer, `.nodeItem >> text=${keyspaceId}`);
|
||||
await safeClick(explorer, `[data-test="${tableId}"] [aria-label="More"]`);
|
||||
await safeClick(explorer, 'button[role="menuitem"]:has-text("Delete Table")');
|
||||
await explorer.fill('text=* Confirm by typing the table id >> input[type="text"]', tableId);
|
||||
await explorer.click('[aria-label="Submit"]');
|
||||
await explorer.click(`[data-test="${keyspaceId}"] [aria-label="More"]`);
|
||||
await explorer.click('button[role="menuitem"]:has-text("Delete Keyspace")');
|
||||
await explorer.click('text=* Confirm by typing the database id >> input[type="text"]');
|
||||
await explorer.fill('text=* Confirm by typing the database id >> input[type="text"]', keyspaceId);
|
||||
await explorer.click("#sidePanelOkButton");
|
||||
await expect(explorer).not.toHaveText(".dataResourceTree", keyspaceId);
|
||||
await expect(explorer).not.toHaveText(".dataResourceTree", tableId);
|
||||
});
|
||||
|
||||
async function clickDBMenu(dbId: string, frame: Frame, retries = 0) {
|
||||
const button = await frame.$(`div[data-test="${dbId}"]`);
|
||||
await button.focus();
|
||||
const handler = await button.asElement();
|
||||
await handler.click();
|
||||
await ensureMenuIsOpen(dbId, frame, retries);
|
||||
return button;
|
||||
}
|
||||
|
||||
async function ensureMenuIsOpen(dbId: string, frame: Frame, retries: number) {
|
||||
await frame.waitFor(RETRY_DELAY);
|
||||
const button = await frame.$(`div[data-test="${dbId}"]`);
|
||||
const classList = await frame.evaluate((button) => {
|
||||
return button.parentElement.classList;
|
||||
}, button);
|
||||
if (!Object.values(classList).includes("selected") && retries < 5) {
|
||||
retries = retries + 1;
|
||||
await clickDBMenu(dbId, frame, retries);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,164 +1,53 @@
|
|||
import "expect-puppeteer";
|
||||
import { Frame } from "puppeteer";
|
||||
import { generateDatabaseName, generateUniqueName, login } from "../utils/shared";
|
||||
import { jest } from "@jest/globals";
|
||||
import "expect-playwright";
|
||||
import { safeClick } from "../utils/safeClick";
|
||||
import { generateUniqueName } from "../utils/shared";
|
||||
jest.setTimeout(240000);
|
||||
|
||||
jest.setTimeout(300000);
|
||||
const LOADING_STATE_DELAY = 2500;
|
||||
const RETRY_DELAY = 5000;
|
||||
const CREATE_DELAY = 10000;
|
||||
const RENDER_DELAY = 1000;
|
||||
test("SQL CRUD", async () => {
|
||||
const databaseId = generateUniqueName("db");
|
||||
const containerId = generateUniqueName("container");
|
||||
|
||||
describe("Collection Add and Delete Mongo spec", () => {
|
||||
it("creates a collection", async () => {
|
||||
try {
|
||||
const dbId = generateDatabaseName();
|
||||
const collectionId = generateUniqueName("col");
|
||||
const sharedKey = `${generateUniqueName()}`;
|
||||
const frame = await login(process.env.MONGO_CONNECTION_STRING);
|
||||
|
||||
// create new collection
|
||||
await frame.waitFor('button[data-test="New Collection"]', { visible: true });
|
||||
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
|
||||
await frame.click('button[data-test="New Collection"]');
|
||||
|
||||
// check new database
|
||||
await frame.waitFor('input[data-test="addCollection-createNewDatabase"]');
|
||||
await frame.click('input[data-test="addCollection-createNewDatabase"]');
|
||||
|
||||
// check shared throughput
|
||||
await frame.waitFor('input[data-test="addCollectionPane-databaseSharedThroughput"]');
|
||||
await frame.click('input[data-test="addCollectionPane-databaseSharedThroughput"]');
|
||||
|
||||
// type database id
|
||||
await frame.waitFor('input[data-test="addCollection-newDatabaseId"]');
|
||||
const dbInput = await frame.$('input[data-test="addCollection-newDatabaseId"]');
|
||||
await dbInput.press("Backspace");
|
||||
await dbInput.type(dbId);
|
||||
|
||||
// type collection id
|
||||
await frame.waitFor('input[data-test="addCollection-collectionId"]');
|
||||
const input = await frame.$('input[data-test="addCollection-collectionId"]');
|
||||
await input.press("Backspace");
|
||||
await input.type(collectionId);
|
||||
|
||||
// type partition key value
|
||||
await frame.waitFor('input[data-test="addCollection-partitionKeyValue"]');
|
||||
const keyInput = await frame.$('input[data-test="addCollection-partitionKeyValue"]');
|
||||
await keyInput.press("Backspace");
|
||||
await keyInput.type(sharedKey);
|
||||
|
||||
// click submit
|
||||
await frame.waitFor("#submitBtnAddCollection");
|
||||
await frame.click("#submitBtnAddCollection");
|
||||
|
||||
// validate created
|
||||
// open database menu
|
||||
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
|
||||
await frame.waitFor(LOADING_STATE_DELAY);
|
||||
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
|
||||
|
||||
const databases = await frame.$$(`div[class="databaseHeader main1 nodeItem "] > div[class="treeNodeHeader "]`);
|
||||
const selectedDbId = await frame.evaluate((element) => {
|
||||
return element.attributes["data-test"].textContent;
|
||||
}, databases[0]);
|
||||
|
||||
await frame.waitFor(`div[data-test="${selectedDbId}"]`), { visible: true };
|
||||
await frame.waitFor(CREATE_DELAY);
|
||||
await frame.waitFor("div[class='rowData'] > span[class='message']");
|
||||
|
||||
const didCreateContainer = await frame.$$eval("div[class='rowData'] > span[class='message']", (elements) => {
|
||||
return elements.some((el) => el.textContent.includes("Successfully created"));
|
||||
});
|
||||
|
||||
expect(didCreateContainer).toBe(true);
|
||||
|
||||
await frame.waitFor(`div[data-test="${selectedDbId}"]`), { visible: true };
|
||||
await frame.waitFor(LOADING_STATE_DELAY);
|
||||
|
||||
await clickDBMenu(selectedDbId, frame);
|
||||
|
||||
const collections = await frame.$$(
|
||||
`div[class="collectionHeader main2 nodeItem "] > div[class="treeNodeHeader "]`
|
||||
);
|
||||
|
||||
if (collections.length) {
|
||||
const textId = await frame.evaluate((element) => {
|
||||
return element.attributes["data-test"].textContent;
|
||||
}, collections[0]);
|
||||
await frame.waitFor(`div[data-test="${textId}"]`, { visible: true });
|
||||
// delete container
|
||||
|
||||
// click context menu for container
|
||||
await frame.waitFor(`div[data-test="${textId}"] > div > button`, { visible: true });
|
||||
await frame.click(`div[data-test="${textId}"] > div > button`);
|
||||
|
||||
// click delete container
|
||||
await frame.waitFor(RENDER_DELAY);
|
||||
await frame.waitFor('span[class="treeComponentMenuItemLabel deleteCollectionMenuItemLabel"]');
|
||||
await frame.click('span[class="treeComponentMenuItemLabel deleteCollectionMenuItemLabel"]');
|
||||
|
||||
// confirm delete container
|
||||
await frame.waitFor('input[id="confirmCollectionId"]', { visible: true });
|
||||
await frame.type('input[id="confirmCollectionId"]', textId);
|
||||
|
||||
// click delete
|
||||
await frame.waitFor('button[id="sidePanelOkButton"]', { visible: true });
|
||||
await frame.click('button[id="sidePanelOkButton"]');
|
||||
await frame.waitFor(LOADING_STATE_DELAY);
|
||||
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
|
||||
|
||||
await expect(page).not.toMatchElement(`div[data-test="${textId}"]`);
|
||||
}
|
||||
|
||||
// click context menu for database
|
||||
await frame.waitFor(`div[data-test="${selectedDbId}"] > div > button`);
|
||||
await frame.waitFor(RENDER_DELAY);
|
||||
const button = await frame.$(`div[data-test="${selectedDbId}"] > div > button`);
|
||||
await button.focus();
|
||||
await button.asElement().click();
|
||||
|
||||
// click delete database
|
||||
await frame.waitFor(RENDER_DELAY);
|
||||
await frame.waitFor('span[class="treeComponentMenuItemLabel deleteDatabaseMenuItemLabel"]');
|
||||
await frame.click('span[class="treeComponentMenuItemLabel deleteDatabaseMenuItemLabel"]');
|
||||
|
||||
// confirm delete database
|
||||
await frame.waitForSelector('input[id="confirmDatabaseId"]', { visible: true });
|
||||
await frame.waitFor(RENDER_DELAY);
|
||||
await frame.type('input[id="confirmDatabaseId"]', selectedDbId);
|
||||
|
||||
// click delete
|
||||
await frame.click('button[id="sidePanelOkButton"]');
|
||||
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
|
||||
await frame.waitFor(LOADING_STATE_DELAY);
|
||||
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
|
||||
await expect(page).not.toMatchElement(`div[data-test="${selectedDbId}"]`);
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const testName = (expect as any).getState().currentTestName;
|
||||
await page.screenshot({ path: `failed-${testName}.jpg` });
|
||||
throw error;
|
||||
}
|
||||
await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-mongo-runner");
|
||||
await page.waitForSelector("iframe");
|
||||
const explorer = page.frame({
|
||||
name: "explorer",
|
||||
});
|
||||
|
||||
// Create new database and collection
|
||||
await explorer.click('[data-test="New Collection"]');
|
||||
await explorer.click('[data-test="addCollection-newDatabaseId"]');
|
||||
await explorer.fill('[data-test="addCollection-newDatabaseId"]', databaseId);
|
||||
await explorer.click('[data-test="addCollection-collectionId"]');
|
||||
await explorer.fill('[data-test="addCollection-collectionId"]', containerId);
|
||||
await explorer.click('[data-test="addCollection-collectionId"]');
|
||||
await explorer.fill('[data-test="addCollection-collectionId"]', containerId);
|
||||
await explorer.click('[data-test="addCollection-partitionKeyValue"]');
|
||||
await explorer.fill('[data-test="addCollection-partitionKeyValue"]', "/pk");
|
||||
await explorer.click('[data-test="addCollection-createCollection"]');
|
||||
await safeClick(explorer, `.nodeItem >> text=${databaseId}`);
|
||||
await safeClick(explorer, `.nodeItem >> text=${containerId}`);
|
||||
// Create indexing policy
|
||||
await safeClick(explorer, ".nodeItem >> text=Settings");
|
||||
await explorer.click('button[role="tab"]:has-text("Indexing Policy")');
|
||||
await explorer.click('[aria-label="Index Field Name 0"]');
|
||||
await explorer.fill('[aria-label="Index Field Name 0"]', "foo");
|
||||
await explorer.click("text=Select an index type");
|
||||
await explorer.click('button[role="option"]:has-text("Single Field")');
|
||||
await explorer.click('[data-test="Save"]');
|
||||
// Remove indexing policy
|
||||
await explorer.click('[aria-label="Delete index Button"]');
|
||||
await explorer.click('[data-test="Save"]');
|
||||
// Delete database and collection
|
||||
await safeClick(explorer, `[data-test="${containerId}"] [aria-label="More"]`);
|
||||
await safeClick(explorer, 'button[role="menuitem"]:has-text("Delete Collection")');
|
||||
await explorer.fill('text=* Confirm by typing the collection id >> input[type="text"]', containerId);
|
||||
await explorer.click('[aria-label="Submit"]');
|
||||
await explorer.click(`[data-test="${databaseId}"] [aria-label="More"]`);
|
||||
await explorer.click('button[role="menuitem"]:has-text("Delete Database")');
|
||||
await explorer.click('text=* Confirm by typing the database id >> input[type="text"]');
|
||||
await explorer.fill('text=* Confirm by typing the database id >> input[type="text"]', databaseId);
|
||||
await explorer.click("#sidePanelOkButton");
|
||||
await expect(explorer).not.toHaveText(".dataResourceTree", databaseId);
|
||||
await expect(explorer).not.toHaveText(".dataResourceTree", containerId);
|
||||
});
|
||||
|
||||
async function clickDBMenu(dbId: string, frame: Frame, retries = 0) {
|
||||
const button = await frame.$(`div[data-test="${dbId}"]`);
|
||||
await button.focus();
|
||||
const handler = await button.asElement();
|
||||
await handler.click();
|
||||
await ensureMenuIsOpen(dbId, frame, retries);
|
||||
return button;
|
||||
}
|
||||
|
||||
async function ensureMenuIsOpen(dbId: string, frame: Frame, retries: number) {
|
||||
await frame.waitFor(RETRY_DELAY);
|
||||
const button = await frame.$(`div[data-test="${dbId}"]`);
|
||||
const classList = await frame.evaluate((button) => {
|
||||
return button.parentElement.classList;
|
||||
}, button);
|
||||
if (!Object.values(classList).includes("selected") && retries < 5) {
|
||||
retries = retries + 1;
|
||||
await clickDBMenu(dbId, frame, retries);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,106 +0,0 @@
|
|||
import "expect-puppeteer";
|
||||
import { createDatabase, generateUniqueName, onClickSaveButton } from "../utils/shared";
|
||||
|
||||
const LOADING_STATE_DELAY = 5000;
|
||||
jest.setTimeout(300000);
|
||||
|
||||
describe("MongoDB Index policy tests", () => {
|
||||
it("Open, Create and Save Index", async () => {
|
||||
try {
|
||||
const singleFieldId = generateUniqueName("key");
|
||||
const wildCardId = generateUniqueName("key") + "$**";
|
||||
await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-mongo-runner");
|
||||
const handle = await page.waitForSelector("iframe");
|
||||
const frame = await handle.contentFrame();
|
||||
const dropDown = "Index Type ";
|
||||
let index = 0;
|
||||
|
||||
//open dataBaseMenu
|
||||
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
|
||||
await frame.waitFor(LOADING_STATE_DELAY);
|
||||
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
|
||||
const { databaseId, collectionId } = await createDatabase(frame);
|
||||
await frame.waitFor(25000);
|
||||
// click on database
|
||||
await frame.waitForSelector(`div[data-test="${databaseId}"]`);
|
||||
await frame.waitFor(LOADING_STATE_DELAY);
|
||||
await frame.click(`div[data-test="${databaseId}"]`);
|
||||
await frame.waitFor(LOADING_STATE_DELAY);
|
||||
|
||||
// click on scale & setting
|
||||
await frame.waitFor(`div[data-test="${collectionId}"]`), { visible: true };
|
||||
await frame.waitFor(LOADING_STATE_DELAY);
|
||||
await frame.click(`div[data-test="${collectionId}"]`);
|
||||
|
||||
await frame.waitFor(`div[data-test="Scale & Settings"]`), { visible: true };
|
||||
await frame.waitFor(10000);
|
||||
await frame.click(`div[data-test="Scale & Settings"]`);
|
||||
|
||||
await frame.waitFor(`button[data-content="Indexing Policy"]`), { visible: true };
|
||||
await frame.waitFor(LOADING_STATE_DELAY);
|
||||
await frame.click(`button[data-content="Indexing Policy"]`);
|
||||
|
||||
// Type to single Field
|
||||
let throughput = await frame.$$(".ms-TextField-field");
|
||||
const selectedDropDownSingleField = dropDown + index;
|
||||
await frame.waitFor(`div[aria-label="${selectedDropDownSingleField}"]`), { visible: true };
|
||||
await throughput[index].type(singleFieldId);
|
||||
await frame.click(`div[aria-label="${selectedDropDownSingleField}"]`);
|
||||
await frame.waitFor(LOADING_STATE_DELAY);
|
||||
await frame.click(`button[title="Single Field"]`);
|
||||
index++;
|
||||
|
||||
// Type to wild card
|
||||
throughput = await frame.$$(".ms-TextField-field");
|
||||
await throughput[index].type(wildCardId);
|
||||
const selectedDropDownWildCard = dropDown + index;
|
||||
await frame.waitFor(`div[aria-label="${selectedDropDownWildCard}"]`), { visible: true };
|
||||
await frame.click(`div[aria-label="${selectedDropDownWildCard}"]`);
|
||||
await frame.waitFor(LOADING_STATE_DELAY);
|
||||
await frame.click(`button[title="Wildcard"]`);
|
||||
index++;
|
||||
|
||||
// click save Button
|
||||
await onClickSaveButton(frame);
|
||||
|
||||
// check the array
|
||||
let singleFieldIndexInserted = false,
|
||||
wildCardIndexInserted = false;
|
||||
await frame.waitFor("div[data-automationid='DetailsRowCell'] > span"), { visible: true };
|
||||
await frame.waitFor(20000);
|
||||
|
||||
const elements = await frame.$$("div[data-automationid='DetailsRowCell'] > span");
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
const element = elements[i];
|
||||
const text = await frame.evaluate((element) => element.textContent, element);
|
||||
if (text.includes(wildCardId)) {
|
||||
wildCardIndexInserted = true;
|
||||
} else if (text.includes(singleFieldId)) {
|
||||
singleFieldIndexInserted = true;
|
||||
}
|
||||
}
|
||||
await frame.waitFor(20000);
|
||||
expect(wildCardIndexInserted).toBe(true);
|
||||
expect(singleFieldIndexInserted).toBe(true);
|
||||
|
||||
//delete all index policy
|
||||
await frame.waitFor("button[aria-label='Delete index Button']"), { visible: true };
|
||||
const deleteButton = await frame.$$("button[aria-label='Delete index Button']");
|
||||
for (let i = 0; i < deleteButton.length; i++) {
|
||||
await frame.click(`button[aria-label="Delete index Button"]`);
|
||||
}
|
||||
await onClickSaveButton(frame);
|
||||
|
||||
//check for cleaning
|
||||
await frame.waitFor(20000);
|
||||
await frame.waitFor("div[data-automationid='DetailsRowCell'] > span"), { visible: true };
|
||||
const isDeletionComplete = await frame.$$("div[data-automationid='DetailsRowCell'] > span");
|
||||
expect(isDeletionComplete).toHaveLength(2);
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const testName = (expect as any).getState().currentTestName;
|
||||
await page.screenshot({ path: `failed-${testName}.jpg` });
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
});
|
|
@ -1,52 +0,0 @@
|
|||
import { ElementHandle, Frame } from "puppeteer";
|
||||
import * as path from "path";
|
||||
|
||||
export const NOTEBOOK_OPERATION_DELAY = 5000;
|
||||
export const RENDER_DELAY = 2500;
|
||||
|
||||
export const uploadNotebookIfNotExist = async (frame: Frame, notebookName: string): Promise<ElementHandle<Element>> => {
|
||||
const notebookNode = await getNotebookNode(frame, notebookName);
|
||||
if (notebookNode) {
|
||||
return notebookNode;
|
||||
}
|
||||
|
||||
const uploadNotebookPath = path.join(__dirname, "testNotebooks", notebookName);
|
||||
const notebookResourceTree = await frame.waitForSelector(".notebookResourceTree");
|
||||
const treeNodeHeadersBeforeUpload = await notebookResourceTree.$$(".treeNodeHeader");
|
||||
|
||||
const ellipses = await treeNodeHeadersBeforeUpload[2].$("button");
|
||||
await ellipses.click();
|
||||
|
||||
await frame.waitFor(RENDER_DELAY);
|
||||
|
||||
const menuItems = await frame.$$(".ms-ContextualMenu-item");
|
||||
await menuItems[4].click();
|
||||
|
||||
const uploadFileButton = await frame.waitForSelector("#importFileButton");
|
||||
uploadFileButton.click();
|
||||
|
||||
const fileChooser = await page.waitForFileChooser();
|
||||
fileChooser.accept([uploadNotebookPath]);
|
||||
|
||||
const submitButton = await frame.waitForSelector("#uploadFileButton");
|
||||
await submitButton.click();
|
||||
|
||||
await frame.waitFor(NOTEBOOK_OPERATION_DELAY);
|
||||
return await getNotebookNode(frame, notebookName);
|
||||
};
|
||||
|
||||
export const getNotebookNode = async (frame: Frame, uploadNotebookName: string): Promise<ElementHandle<Element>> => {
|
||||
const notebookResourceTree = await frame.waitForSelector(".notebookResourceTree");
|
||||
await frame.waitFor(RENDER_DELAY);
|
||||
let currentNotebookNode: ElementHandle<Element>;
|
||||
|
||||
const treeNodeHeaders = await notebookResourceTree.$$(".treeNodeHeader");
|
||||
for (let i = 1; i < treeNodeHeaders.length; i++) {
|
||||
currentNotebookNode = treeNodeHeaders[i];
|
||||
const nodeLabel = await currentNotebookNode.$eval(".nodeLabel", (element) => element.textContent);
|
||||
if (nodeLabel === uploadNotebookName) {
|
||||
return currentNotebookNode;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
};
|
|
@ -0,0 +1,27 @@
|
|||
import { jest } from "@jest/globals";
|
||||
import "expect-playwright";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
jest.setTimeout(240000);
|
||||
|
||||
const filename = "GettingStarted.ipynb";
|
||||
const fileToUpload = `GettingStarted-ignore${Math.floor(Math.random() * 100000)}.ipynb`;
|
||||
|
||||
fs.copyFileSync(path.join(__dirname, filename), path.join(__dirname, fileToUpload));
|
||||
|
||||
test("Notebooks", async () => {
|
||||
await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-sql-runner");
|
||||
await page.waitForSelector("iframe");
|
||||
const explorer = page.frame({
|
||||
name: "explorer",
|
||||
});
|
||||
// Upload and Delete Notebook
|
||||
await explorer.click('[data-test="My Notebooks"] [aria-label="More"]');
|
||||
await explorer.click('button[role="menuitem"]:has-text("Upload File")');
|
||||
await explorer.setInputFiles("#importFileInput", path.join(__dirname, fileToUpload));
|
||||
await explorer.click('[aria-label="Submit"]');
|
||||
await explorer.click(`[data-test="${fileToUpload}"] [aria-label="More"]`);
|
||||
await explorer.click('button[role="menuitem"]:has-text("Delete")');
|
||||
await explorer.click('button:has-text("Delete")');
|
||||
await expect(explorer).not.toHaveText(".notebookResourceTree", fileToUpload);
|
||||
});
|
|
@ -1,28 +0,0 @@
|
|||
import { uploadNotebookIfNotExist } from "./notebookTestUtils";
|
||||
|
||||
jest.setTimeout(300000);
|
||||
|
||||
const notebookName = "GettingStarted.ipynb";
|
||||
|
||||
describe("Notebook UI tests", () => {
|
||||
it("Upload, Open and Delete Notebook", async () => {
|
||||
try {
|
||||
await page.goto("https://localhost:1234/testExplorer.html");
|
||||
const handle = await page.waitForSelector("iframe");
|
||||
const frame = await handle.contentFrame();
|
||||
await frame.waitForSelector(".galleryHeader");
|
||||
const uploadedNotebookNode = await uploadNotebookIfNotExist(frame, notebookName);
|
||||
await uploadedNotebookNode.click();
|
||||
await frame.waitForSelector(".tabNavText");
|
||||
const tabTitle = await frame.$eval(".tabNavText", (element) => element.textContent);
|
||||
expect(tabTitle).toEqual(notebookName);
|
||||
const closeIcon = await frame.waitForSelector(".close-Icon");
|
||||
await closeIcon.click();
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const testName = (expect as any).getState().currentTestName;
|
||||
await page.screenshot({ path: `Test Failed ${testName}.jpg` });
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
});
|
|
@ -0,0 +1,26 @@
|
|||
const PlaywrightEnvironment = require("jest-playwright-preset/lib/PlaywrightEnvironment").default;
|
||||
|
||||
class CustomEnvironment extends PlaywrightEnvironment {
|
||||
async setup() {
|
||||
await super.setup();
|
||||
// Your setup
|
||||
}
|
||||
|
||||
async teardown() {
|
||||
// Your teardown
|
||||
await super.teardown();
|
||||
}
|
||||
|
||||
async handleTestEvent(event) {
|
||||
if (event.name === "test_done" && event.test.errors.length > 0) {
|
||||
const parentName = event.test.parent.name.replace(/\W/g, "-");
|
||||
const specName = event.test.name.replace(/\W/g, "-");
|
||||
|
||||
await this.global.page.screenshot({
|
||||
path: `screenshots/${parentName}_${specName}.png`,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = CustomEnvironment;
|
|
@ -1,47 +1,36 @@
|
|||
jest.setTimeout(300000);
|
||||
test("Self Serve", async () => {
|
||||
await page.goto("https://localhost:1234/testExplorer.html?iframeSrc=selfServe.html");
|
||||
const handle = await page.waitForSelector("iframe");
|
||||
const frame = await handle.contentFrame();
|
||||
|
||||
describe("Self Serve", () => {
|
||||
it("Launch Self Serve Example", async () => {
|
||||
try {
|
||||
await page.goto("https://localhost:1234/testExplorer.html?iframeSrc=selfServe.html");
|
||||
const handle = await page.waitForSelector("iframe");
|
||||
const frame = await handle.contentFrame();
|
||||
// wait for refresh RP call to end
|
||||
await page.waitForTimeout(10000);
|
||||
|
||||
// wait for refresh RP call to end
|
||||
await frame.waitFor(10000);
|
||||
// id of the display element is in the format {PROPERTY_NAME}-{DISPLAY_NAME}-{DISPLAY_TYPE}
|
||||
await frame.waitForSelector("#description-text-display");
|
||||
|
||||
// id of the display element is in the format {PROPERTY_NAME}-{DISPLAY_NAME}-{DISPLAY_TYPE}
|
||||
await frame.waitForSelector("#description-text-display");
|
||||
const regions = await frame.waitForSelector("#regions-dropdown-input");
|
||||
|
||||
const regions = await frame.waitForSelector("#regions-dropdown-input");
|
||||
const currentRegionsDescription = await frame.$$("#currentRegionText-text-display");
|
||||
expect(currentRegionsDescription).toHaveLength(0);
|
||||
let disabledLoggingToggle = await frame.$$("#enableLogging-toggle-input[disabled]");
|
||||
expect(disabledLoggingToggle).toHaveLength(0);
|
||||
|
||||
const currentRegionsDescription = await frame.$$("#currentRegionText-text-display");
|
||||
expect(currentRegionsDescription).toHaveLength(0);
|
||||
let disabledLoggingToggle = await frame.$$("#enableLogging-toggle-input[disabled]");
|
||||
expect(disabledLoggingToggle).toHaveLength(0);
|
||||
await regions.click();
|
||||
const regionsDropdownElement1 = await frame.waitForSelector("#regions-dropdown-input-list0");
|
||||
await regionsDropdownElement1.click();
|
||||
|
||||
await regions.click();
|
||||
const regionsDropdownElement1 = await frame.waitForSelector("#regions-dropdown-input-list0");
|
||||
await regionsDropdownElement1.click();
|
||||
await frame.waitForSelector("#currentRegionText-text-display");
|
||||
disabledLoggingToggle = await frame.$$("#enableLogging-toggle-input[disabled]");
|
||||
expect(disabledLoggingToggle).toHaveLength(1);
|
||||
|
||||
await frame.waitForSelector("#currentRegionText-text-display");
|
||||
disabledLoggingToggle = await frame.$$("#enableLogging-toggle-input[disabled]");
|
||||
expect(disabledLoggingToggle).toHaveLength(1);
|
||||
await frame.waitForSelector("#accountName-textField-input");
|
||||
|
||||
await frame.waitForSelector("#accountName-textField-input");
|
||||
const enableDbLevelThroughput = await frame.waitForSelector("#enableDbLevelThroughput-toggle-input");
|
||||
const dbThroughput = await frame.$$("#dbThroughput-slider-input");
|
||||
expect(dbThroughput).toHaveLength(0);
|
||||
await enableDbLevelThroughput.click();
|
||||
await frame.waitForSelector("#dbThroughput-slider-input");
|
||||
|
||||
const enableDbLevelThroughput = await frame.waitForSelector("#enableDbLevelThroughput-toggle-input");
|
||||
const dbThroughput = await frame.$$("#dbThroughput-slider-input");
|
||||
expect(dbThroughput).toHaveLength(0);
|
||||
await enableDbLevelThroughput.click();
|
||||
await frame.waitForSelector("#dbThroughput-slider-input");
|
||||
|
||||
await frame.waitForSelector("#collectionThroughput-spinner-input");
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const testName = (expect as any).getState().currentTestName;
|
||||
await page.screenshot({ path: `Test Failed ${testName}.jpg` });
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
await frame.waitForSelector("#collectionThroughput-spinner-input");
|
||||
});
|
||||
|
|
|
@ -1,163 +1,39 @@
|
|||
import "expect-puppeteer";
|
||||
import { Frame } from "puppeteer";
|
||||
import { generateDatabaseName, generateUniqueName } from "../utils/shared";
|
||||
import { jest } from "@jest/globals";
|
||||
import "expect-playwright";
|
||||
import { safeClick } from "../utils/safeClick";
|
||||
import { generateUniqueName } from "../utils/shared";
|
||||
jest.setTimeout(120000);
|
||||
|
||||
jest.setTimeout(300000);
|
||||
const LOADING_STATE_DELAY = 2500;
|
||||
const RETRY_DELAY = 5000;
|
||||
const CREATE_DELAY = 10000;
|
||||
const RENDER_DELAY = 1000;
|
||||
test("SQL CRUD", async () => {
|
||||
const databaseId = generateUniqueName("db");
|
||||
const containerId = generateUniqueName("container");
|
||||
|
||||
describe("Collection Add and Delete SQL spec", () => {
|
||||
it("creates a collection", async () => {
|
||||
try {
|
||||
const dbId = generateDatabaseName();
|
||||
const collectionId = generateUniqueName("col");
|
||||
const sharedKey = `/skey${generateUniqueName()}`;
|
||||
await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-sql-runner");
|
||||
const handle = await page.waitForSelector("iframe");
|
||||
const frame = await handle.contentFrame();
|
||||
|
||||
// create new collection
|
||||
await frame.waitFor('button[data-test="New Container"]', { visible: true });
|
||||
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
|
||||
await frame.click('button[data-test="New Container"]');
|
||||
|
||||
// check new database
|
||||
await frame.waitFor('input[data-test="addCollection-createNewDatabase"]');
|
||||
await frame.click('input[data-test="addCollection-createNewDatabase"]');
|
||||
|
||||
// check shared throughput
|
||||
await frame.waitFor('input[data-test="addCollectionPane-databaseSharedThroughput"]');
|
||||
await frame.click('input[data-test="addCollectionPane-databaseSharedThroughput"]');
|
||||
|
||||
// type database id
|
||||
await frame.waitFor('input[data-test="addCollection-newDatabaseId"]');
|
||||
const dbInput = await frame.$('input[data-test="addCollection-newDatabaseId"]');
|
||||
await dbInput.press("Backspace");
|
||||
await dbInput.type(dbId);
|
||||
|
||||
// type collection id
|
||||
await frame.waitFor('input[data-test="addCollection-collectionId"]');
|
||||
const input = await frame.$('input[data-test="addCollection-collectionId"]');
|
||||
await input.press("Backspace");
|
||||
await input.type(collectionId);
|
||||
|
||||
// type partition key value
|
||||
await frame.waitFor('input[data-test="addCollection-partitionKeyValue"]');
|
||||
const keyInput = await frame.$('input[data-test="addCollection-partitionKeyValue"]');
|
||||
await keyInput.press("Backspace");
|
||||
await keyInput.type(sharedKey);
|
||||
|
||||
// click submit
|
||||
await frame.waitFor("#submitBtnAddCollection");
|
||||
await frame.click("#submitBtnAddCollection");
|
||||
|
||||
// validate created
|
||||
// open database menu
|
||||
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
|
||||
await frame.waitFor(CREATE_DELAY);
|
||||
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
|
||||
const databases = await frame.$$(`div[class="databaseHeader main1 nodeItem "] > div[class="treeNodeHeader "]`);
|
||||
const selectedDbId = await frame.evaluate((element) => {
|
||||
return element.attributes["data-test"].textContent;
|
||||
}, databases[0]);
|
||||
|
||||
await frame.waitFor(`div[data-test="${selectedDbId}"]`), { visible: true };
|
||||
await frame.waitFor(CREATE_DELAY);
|
||||
await frame.waitFor("div[class='rowData'] > span[class='message']");
|
||||
|
||||
await frame.waitFor(`div[data-test="${selectedDbId}"]`), { visible: true };
|
||||
await frame.waitFor(LOADING_STATE_DELAY);
|
||||
|
||||
await clickDBMenu(selectedDbId, frame);
|
||||
|
||||
const collections = await frame.$$(
|
||||
`div[class="collectionHeader main2 nodeItem "] > div[class="treeNodeHeader "]`
|
||||
);
|
||||
|
||||
if (collections.length) {
|
||||
await frame.waitFor(`div[class="collectionHeader main2 nodeItem "] > div[class="treeNodeHeader "]`, {
|
||||
visible: true,
|
||||
});
|
||||
|
||||
const textId = await frame.evaluate((element) => {
|
||||
return element.attributes["data-test"].textContent;
|
||||
}, collections[0]);
|
||||
await frame.waitFor(`div[data-test="${textId}"]`, { visible: true });
|
||||
// delete container
|
||||
|
||||
// click context menu for container
|
||||
await frame.waitFor(`div[data-test="${textId}"] > div > button`, { visible: true });
|
||||
await frame.click(`div[data-test="${textId}"] > div > button`);
|
||||
|
||||
// click delete container
|
||||
await frame.waitFor(RENDER_DELAY);
|
||||
await frame.waitFor('span[class="treeComponentMenuItemLabel deleteCollectionMenuItemLabel"]');
|
||||
await frame.click('span[class="treeComponentMenuItemLabel deleteCollectionMenuItemLabel"]');
|
||||
|
||||
// confirm delete container
|
||||
await frame.waitFor('input[id="confirmCollectionId"]', { visible: true });
|
||||
await frame.type('input[id="confirmCollectionId"]', textId);
|
||||
|
||||
// click delete
|
||||
await frame.waitFor("button.genericPaneSubmitBtn", { visible: true });
|
||||
await frame.click("button.genericPaneSubmitBtn");
|
||||
await frame.waitFor(LOADING_STATE_DELAY);
|
||||
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
|
||||
|
||||
await expect(page).not.toMatchElement(`div[data-test="${textId}"]`);
|
||||
}
|
||||
|
||||
// click context menu for database
|
||||
await frame.waitFor(`div[data-test="${selectedDbId}"] > div > button`);
|
||||
await frame.waitFor(CREATE_DELAY);
|
||||
const button = await frame.$(`div[data-test="${selectedDbId}"] > div > button`);
|
||||
await button.focus();
|
||||
await button.asElement().click();
|
||||
|
||||
// click delete database
|
||||
await frame.waitFor(CREATE_DELAY);
|
||||
await frame.waitFor('span[class="treeComponentMenuItemLabel deleteDatabaseMenuItemLabel"]');
|
||||
await frame.click('span[class="treeComponentMenuItemLabel deleteDatabaseMenuItemLabel"]');
|
||||
|
||||
// confirm delete database
|
||||
await frame.waitForSelector('input[id="confirmDatabaseId"]', { visible: true });
|
||||
await frame.waitFor(CREATE_DELAY);
|
||||
await frame.type('input[id="confirmDatabaseId"]', selectedDbId);
|
||||
|
||||
// click delete
|
||||
await frame.click('button[id="sidePanelOkButton"]');
|
||||
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
|
||||
await frame.waitFor(CREATE_DELAY);
|
||||
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
|
||||
await expect(page).not.toMatchElement(`div[data-test="${selectedDbId}"]`);
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const testName = (expect as any).getState().currentTestName;
|
||||
await page.screenshot({ path: `failed-${testName}.jpg` });
|
||||
throw error;
|
||||
}
|
||||
await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-sql-runner");
|
||||
await page.waitForSelector("iframe");
|
||||
const explorer = page.frame({
|
||||
name: "explorer",
|
||||
});
|
||||
|
||||
await explorer.click('[data-test="New Container"]');
|
||||
await explorer.click('[data-test="addCollection-newDatabaseId"]');
|
||||
await explorer.fill('[data-test="addCollection-newDatabaseId"]', databaseId);
|
||||
await explorer.click('[data-test="addCollection-collectionId"]');
|
||||
await explorer.fill('[data-test="addCollection-collectionId"]', containerId);
|
||||
await explorer.click('[data-test="addCollection-collectionId"]');
|
||||
await explorer.fill('[data-test="addCollection-collectionId"]', containerId);
|
||||
await explorer.click('[data-test="addCollection-partitionKeyValue"]');
|
||||
await explorer.fill('[data-test="addCollection-partitionKeyValue"]', "/pk");
|
||||
await explorer.click('[data-test="addCollection-createCollection"]');
|
||||
await safeClick(explorer, `.nodeItem >> text=${databaseId}`);
|
||||
await safeClick(explorer, `[data-test="${containerId}"] [aria-label="More"]`);
|
||||
await safeClick(explorer, 'button[role="menuitem"]:has-text("Delete Container")');
|
||||
await explorer.fill('text=* Confirm by typing the container id >> input[type="text"]', containerId);
|
||||
await explorer.click('[aria-label="Submit"]');
|
||||
await explorer.click(`[data-test="${databaseId}"] [aria-label="More"]`);
|
||||
await explorer.click('button[role="menuitem"]:has-text("Delete Database")');
|
||||
await explorer.click('text=* Confirm by typing the database id >> input[type="text"]');
|
||||
await explorer.fill('text=* Confirm by typing the database id >> input[type="text"]', databaseId);
|
||||
await explorer.click("#sidePanelOkButton");
|
||||
await expect(explorer).not.toHaveText(".dataResourceTree", databaseId);
|
||||
await expect(explorer).not.toHaveText(".dataResourceTree", containerId);
|
||||
});
|
||||
|
||||
async function clickDBMenu(dbId: string, frame: Frame, retries = 0) {
|
||||
const button = await frame.$(`div[data-test="${dbId}"]`);
|
||||
await button.focus();
|
||||
const handler = await button.asElement();
|
||||
await handler.click();
|
||||
await ensureMenuIsOpen(dbId, frame, retries);
|
||||
return button;
|
||||
}
|
||||
|
||||
async function ensureMenuIsOpen(dbId: string, frame: Frame, retries: number) {
|
||||
await frame.waitFor(RETRY_DELAY);
|
||||
const button = await frame.$(`div[data-test="${dbId}"]`);
|
||||
const classList = await frame.evaluate((button) => {
|
||||
return button.parentElement.classList;
|
||||
}, button);
|
||||
if (!Object.values(classList).includes("selected") && retries < 5) {
|
||||
retries = retries + 1;
|
||||
await clickDBMenu(dbId, frame, retries);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,83 +1,46 @@
|
|||
/* eslint-disable jest/expect-expect */
|
||||
import "expect-puppeteer";
|
||||
import { Frame } from "puppeteer";
|
||||
import { generateDatabaseName, generateUniqueName } from "../utils/shared";
|
||||
import { CosmosClient, PermissionMode } from "@azure/cosmos";
|
||||
import { CosmosDBManagementClient } from "@azure/arm-cosmosdb";
|
||||
import { CosmosClient, PermissionMode } from "@azure/cosmos";
|
||||
import * as msRestNodeAuth from "@azure/ms-rest-nodeauth";
|
||||
import { jest } from "@jest/globals";
|
||||
import "expect-playwright";
|
||||
import { generateDatabaseName, generateUniqueName } from "../utils/shared";
|
||||
jest.setTimeout(120000);
|
||||
|
||||
const clientId = process.env["NOTEBOOKS_TEST_RUNNER_CLIENT_ID"];
|
||||
const clientId = "fd8753b0-0707-4e32-84e9-2532af865fb4";
|
||||
const secret = process.env["NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET"];
|
||||
const tenantId = "72f988bf-86f1-41af-91ab-2d7cd011db47";
|
||||
const subscriptionId = "69e02f2d-f059-4409-9eac-97e8a276ae2c";
|
||||
const resourceGroupName = "runners";
|
||||
|
||||
jest.setTimeout(300000);
|
||||
const RETRY_DELAY = 5000;
|
||||
const CREATE_DELAY = 10000;
|
||||
|
||||
describe("Collection Add and Delete SQL spec", () => {
|
||||
it("creates a collection", async () => {
|
||||
const credentials = await msRestNodeAuth.loginWithServicePrincipalSecret(clientId, secret, tenantId);
|
||||
const armClient = new CosmosDBManagementClient(credentials, subscriptionId);
|
||||
const account = await armClient.databaseAccounts.get(resourceGroupName, "portal-sql-runner");
|
||||
const keys = await armClient.databaseAccounts.listKeys(resourceGroupName, "portal-sql-runner");
|
||||
const dbId = generateDatabaseName();
|
||||
const collectionId = generateUniqueName("col");
|
||||
const client = new CosmosClient({
|
||||
endpoint: account.documentEndpoint,
|
||||
key: keys.primaryMasterKey,
|
||||
});
|
||||
const { database } = await client.databases.createIfNotExists({ id: dbId });
|
||||
const { container } = await database.containers.createIfNotExists({ id: collectionId });
|
||||
const { user } = await database.users.upsert({ id: "testUser" });
|
||||
const { resource: containerPermission } = await user.permissions.upsert({
|
||||
id: "partitionLevelPermission",
|
||||
permissionMode: PermissionMode.All,
|
||||
resource: container.url,
|
||||
});
|
||||
const resourceTokenConnectionString = `AccountEndpoint=${account.documentEndpoint};DatabaseId=${database.id};CollectionId=${container.id};${containerPermission._token}`;
|
||||
try {
|
||||
await page.goto(process.env.DATA_EXPLORER_ENDPOINT);
|
||||
await page.waitFor("div > p.switchConnectTypeText", { visible: true });
|
||||
await page.click("div > p.switchConnectTypeText");
|
||||
await page.type("input[class='inputToken']", resourceTokenConnectionString);
|
||||
await page.click("input[value='Connect']");
|
||||
const handle = await page.waitForSelector("iframe");
|
||||
const frame = await handle.contentFrame();
|
||||
// validate created
|
||||
// open database menu
|
||||
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
|
||||
await frame.waitFor(CREATE_DELAY);
|
||||
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
|
||||
await frame.$$(`div[class="databaseHeader main1 nodeItem "] > div[class="treeNodeHeader "]`);
|
||||
expect(await frame.$(`span[title="${collectionId}"]`)).toBeDefined();
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const testName = (expect as any).getState().currentTestName;
|
||||
await page.screenshot({ path: `failed-${testName}.jpg` });
|
||||
throw error;
|
||||
}
|
||||
test("Resource token", async () => {
|
||||
const credentials = await msRestNodeAuth.loginWithServicePrincipalSecret(clientId, secret, tenantId);
|
||||
const armClient = new CosmosDBManagementClient(credentials, subscriptionId);
|
||||
const account = await armClient.databaseAccounts.get(resourceGroupName, "portal-sql-runner");
|
||||
const keys = await armClient.databaseAccounts.listKeys(resourceGroupName, "portal-sql-runner");
|
||||
const dbId = generateDatabaseName();
|
||||
const collectionId = generateUniqueName("col");
|
||||
const client = new CosmosClient({
|
||||
endpoint: account.documentEndpoint,
|
||||
key: keys.primaryMasterKey,
|
||||
});
|
||||
const { database } = await client.databases.createIfNotExists({ id: dbId });
|
||||
const { container } = await database.containers.createIfNotExists({ id: collectionId });
|
||||
const { user } = await database.users.upsert({ id: "testUser" });
|
||||
const { resource: containerPermission } = await user.permissions.upsert({
|
||||
id: "partitionLevelPermission",
|
||||
permissionMode: PermissionMode.All,
|
||||
resource: container.url,
|
||||
});
|
||||
const resourceTokenConnectionString = `AccountEndpoint=${account.documentEndpoint};DatabaseId=${database.id};CollectionId=${container.id};${containerPermission._token}`;
|
||||
|
||||
await page.goto("https://localhost:1234/hostedExplorer.html");
|
||||
await page.waitForSelector("div > p.switchConnectTypeText");
|
||||
await page.click("div > p.switchConnectTypeText");
|
||||
await page.type("input[class='inputToken']", resourceTokenConnectionString);
|
||||
await page.click("input[value='Connect']");
|
||||
await page.waitForSelector("iframe");
|
||||
const explorer = page.frame({
|
||||
name: "explorer",
|
||||
});
|
||||
await explorer.textContent(`css=.dataResourceTree >> "${collectionId}"`);
|
||||
});
|
||||
|
||||
async function clickDBMenu(dbId: string, frame: Frame, retries = 0) {
|
||||
const button = await frame.$(`div[data-test="${dbId}"]`);
|
||||
await button.focus();
|
||||
const handler = await button.asElement();
|
||||
await handler.click();
|
||||
await ensureMenuIsOpen(dbId, frame, retries);
|
||||
return button;
|
||||
}
|
||||
|
||||
async function ensureMenuIsOpen(dbId: string, frame: Frame, retries: number) {
|
||||
await frame.waitFor(RETRY_DELAY);
|
||||
const button = await frame.$(`div[data-test="${dbId}"]`);
|
||||
const classList = await frame.evaluate((button) => {
|
||||
return button.parentElement.classList;
|
||||
}, button);
|
||||
if (!Object.values(classList).includes("selected") && retries < 5) {
|
||||
retries = retries + 1;
|
||||
await clickDBMenu(dbId, frame, retries);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,111 +1,27 @@
|
|||
import "expect-puppeteer";
|
||||
import { Frame } from "puppeteer";
|
||||
import { generateUniqueName, login } from "../utils/shared";
|
||||
import { jest } from "@jest/globals";
|
||||
import "expect-playwright";
|
||||
import { safeClick } from "../utils/safeClick";
|
||||
import { generateUniqueName } from "../utils/shared";
|
||||
|
||||
jest.setTimeout(300000);
|
||||
const RETRY_DELAY = 5000;
|
||||
const LOADING_STATE_DELAY = 2500;
|
||||
const RENDER_DELAY = 1000;
|
||||
jest.setTimeout(120000);
|
||||
|
||||
describe("Collection Add and Delete Tables spec", () => {
|
||||
it("creates a collection", async () => {
|
||||
try {
|
||||
const tableId = generateUniqueName("tab");
|
||||
const frame = await login(process.env.TABLES_CONNECTION_STRING);
|
||||
test("Tables CRUD", async () => {
|
||||
const tableId = generateUniqueName("table");
|
||||
|
||||
// create new collection
|
||||
await frame.waitFor('button[data-test="New Table"]', { visible: true });
|
||||
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
|
||||
await frame.click('button[data-test="New Table"]');
|
||||
|
||||
// type database id
|
||||
await frame.waitFor('input[data-test="addCollection-newDatabaseId"]');
|
||||
const dbInput = await frame.$('input[data-test="addCollection-newDatabaseId"]');
|
||||
await dbInput.press("Backspace");
|
||||
await dbInput.type(tableId);
|
||||
|
||||
// click submit
|
||||
await frame.waitFor("#submitBtnAddCollection");
|
||||
await frame.click("#submitBtnAddCollection");
|
||||
|
||||
// validate created
|
||||
// open database menu
|
||||
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
|
||||
await frame.waitFor(LOADING_STATE_DELAY);
|
||||
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
|
||||
|
||||
await frame.waitFor(`div[data-test="TablesDB"]`), { visible: true };
|
||||
await frame.waitFor(LOADING_STATE_DELAY);
|
||||
|
||||
const didCreateContainer = await frame.$$eval("div[class='rowData'] > span[class='message']", (elements) => {
|
||||
return elements.some((el) => el.textContent.includes("Successfully created"));
|
||||
});
|
||||
|
||||
expect(didCreateContainer).toBe(true);
|
||||
|
||||
await frame.waitFor(`div[data-test="TablesDB"]`), { visible: true };
|
||||
await frame.waitFor(LOADING_STATE_DELAY);
|
||||
|
||||
await clickTablesMenu(frame);
|
||||
|
||||
const collections = await frame.$$(
|
||||
`div[class="collectionHeader main2 nodeItem "] > div[class="treeNodeHeader "]`
|
||||
);
|
||||
const textId = await frame.evaluate((element) => {
|
||||
return element.attributes["data-test"].textContent;
|
||||
}, collections[0]);
|
||||
await frame.waitFor(`div[data-test="${textId}"]`, { visible: true });
|
||||
|
||||
// delete container
|
||||
|
||||
// click context menu for container
|
||||
await frame.waitFor(`div[data-test="${textId}"] > div > button`, { visible: true });
|
||||
await frame.click(`div[data-test="${textId}"] > div > button`);
|
||||
|
||||
// click delete container
|
||||
await frame.waitFor(RENDER_DELAY);
|
||||
await frame.waitFor('span[class="treeComponentMenuItemLabel deleteCollectionMenuItemLabel"]');
|
||||
await frame.click('span[class="treeComponentMenuItemLabel deleteCollectionMenuItemLabel"]');
|
||||
|
||||
// confirm delete container
|
||||
await frame.waitFor('input[id="confirmCollectionId"]', { visible: true });
|
||||
await frame.type('input[id="confirmCollectionId"]', textId);
|
||||
|
||||
// click delete
|
||||
await frame.waitFor("button.genericPaneSubmitBtn", { visible: true });
|
||||
await frame.click("button.genericPaneSubmitBtn");
|
||||
await frame.waitFor(LOADING_STATE_DELAY);
|
||||
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
|
||||
await frame.waitFor(LOADING_STATE_DELAY);
|
||||
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
|
||||
|
||||
await expect(page).not.toMatchElement(`div[data-test="${textId}"]`);
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const testName = (expect as any).getState().currentTestName;
|
||||
await page.screenshot({ path: `failed-${testName}.jpg` });
|
||||
throw error;
|
||||
}
|
||||
await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-tables-runner");
|
||||
await page.waitForSelector("iframe");
|
||||
const explorer = page.frame({
|
||||
name: "explorer",
|
||||
});
|
||||
|
||||
await explorer.click('[data-test="New Table"]');
|
||||
await explorer.click('[data-test="addCollection-collectionId"]');
|
||||
await explorer.fill('[data-test="addCollection-collectionId"]', tableId);
|
||||
await explorer.click('[data-test="addCollection-createCollection"]');
|
||||
await safeClick(explorer, `[data-test="TablesDB"]`);
|
||||
await safeClick(explorer, `[data-test="${tableId}"] [aria-label="More"]`);
|
||||
await safeClick(explorer, 'button[role="menuitem"]:has-text("Delete Table")');
|
||||
await explorer.fill('text=* Confirm by typing the table id >> input[type="text"]', tableId);
|
||||
await explorer.click('[aria-label="Submit"]');
|
||||
await expect(explorer).not.toHaveText(".dataResourceTree", tableId);
|
||||
});
|
||||
|
||||
async function clickTablesMenu(frame: Frame, retries = 0) {
|
||||
const button = await frame.$(`div[data-test="TablesDB"]`);
|
||||
await button.focus();
|
||||
const handler = await button.asElement();
|
||||
await handler.click();
|
||||
await ensureMenuIsOpen(frame, retries);
|
||||
return button;
|
||||
}
|
||||
|
||||
async function ensureMenuIsOpen(frame: Frame, retries: number) {
|
||||
await frame.waitFor(RETRY_DELAY);
|
||||
const button = await frame.$(`div[data-test="TablesDB"]`);
|
||||
const classList = await frame.evaluate((button) => {
|
||||
return button.parentElement.classList;
|
||||
}, button);
|
||||
if (!Object.values(classList).includes("selected") && retries < 5) {
|
||||
retries = retries + 1;
|
||||
await clickTablesMenu(frame, retries);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
import { Frame } from "playwright";
|
||||
|
||||
export async function safeClick(page: Frame, selector: string): Promise<void> {
|
||||
// TODO: Remove. Playwright does this for you... mostly.
|
||||
// But our knockout+react setup sometimes leaves dom nodes detached and even playwright can't recover.
|
||||
// Resource tree is particually bad.
|
||||
// Ideally this should only be added as a last resort
|
||||
await page.waitForSelector(selector);
|
||||
await page.waitForTimeout(5000);
|
||||
await page.click(selector);
|
||||
}
|
|
@ -1,26 +1,4 @@
|
|||
import crypto from "crypto";
|
||||
import { Frame } from "puppeteer";
|
||||
|
||||
const LOADING_STATE_DELAY = 3000;
|
||||
const CREATE_DELAY = 10000;
|
||||
|
||||
export async function login(connectionString: string): Promise<Frame> {
|
||||
const prodUrl = process.env.DATA_EXPLORER_ENDPOINT;
|
||||
await page.goto(prodUrl);
|
||||
|
||||
if (process.env.PLATFORM === "Emulator") {
|
||||
return page.mainFrame();
|
||||
}
|
||||
// log in with connection string
|
||||
await page.waitFor("div > p.switchConnectTypeText", { visible: true });
|
||||
await page.click("div > p.switchConnectTypeText");
|
||||
const connStr = connectionString;
|
||||
await page.type("input[class='inputToken']", connStr);
|
||||
await page.click("input[value='Connect']");
|
||||
const handle = await page.waitForSelector("iframe");
|
||||
const frame = await handle.contentFrame();
|
||||
return frame;
|
||||
}
|
||||
|
||||
export function generateUniqueName(baseName = "", length = 4): string {
|
||||
return `${baseName}${crypto.randomBytes(length).toString("hex")}`;
|
||||
|
@ -29,50 +7,3 @@ export function generateUniqueName(baseName = "", length = 4): string {
|
|||
export function generateDatabaseName(baseName = "db", length = 1): string {
|
||||
return `${baseName}${crypto.randomBytes(length).toString("hex")}-${Date.now()}`;
|
||||
}
|
||||
|
||||
export async function createDatabase(frame: Frame): Promise<{ databaseId: string; collectionId: string }> {
|
||||
const databaseId = generateDatabaseName();
|
||||
const collectionId = generateUniqueName("col");
|
||||
const shardKey = "partitionKey";
|
||||
// create new collection
|
||||
await frame.waitFor('button[data-test="New Collection"]', { visible: true });
|
||||
await frame.click('button[data-test="New Collection"]');
|
||||
|
||||
// check new database
|
||||
await frame.waitFor('input[data-test="addCollection-createNewDatabase"]');
|
||||
await frame.click('input[data-test="addCollection-createNewDatabase"]');
|
||||
|
||||
// check shared throughput
|
||||
await frame.waitFor('input[data-test="addCollectionPane-databaseSharedThroughput"]');
|
||||
await frame.click('input[data-test="addCollectionPane-databaseSharedThroughput"]');
|
||||
|
||||
// type database id
|
||||
await frame.waitFor('input[data-test="addCollection-newDatabaseId"]');
|
||||
const dbInput = await frame.$('input[data-test="addCollection-newDatabaseId"]');
|
||||
await dbInput.press("Backspace");
|
||||
await dbInput.type(databaseId);
|
||||
|
||||
// type collection id
|
||||
await frame.waitFor('input[data-test="addCollection-collectionId"]');
|
||||
const input = await frame.$('input[data-test="addCollection-collectionId"]');
|
||||
await input.press("Backspace");
|
||||
await input.type(collectionId);
|
||||
|
||||
// type partition key value
|
||||
await frame.waitFor('input[data-test="addCollection-partitionKeyValue"]');
|
||||
const keyInput = await frame.$('input[data-test="addCollection-partitionKeyValue"]');
|
||||
await keyInput.press("Backspace");
|
||||
await keyInput.type(shardKey);
|
||||
|
||||
// click submit
|
||||
await frame.waitFor("#submitBtnAddCollection");
|
||||
await frame.click("#submitBtnAddCollection");
|
||||
return { databaseId, collectionId };
|
||||
}
|
||||
|
||||
export async function onClickSaveButton(frame: Frame): Promise<void> {
|
||||
await frame.waitFor(`button[data-test="Save"]`), { visible: true };
|
||||
await frame.waitFor(LOADING_STATE_DELAY);
|
||||
await frame.click(`button[data-test="Save"]`);
|
||||
await frame.waitFor(CREATE_DELAY);
|
||||
}
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
const { AxePuppeteer } = require("axe-puppeteer");
|
||||
const puppeteer = require("puppeteer");
|
||||
|
||||
(async () => {
|
||||
const browser = await puppeteer.launch({ ignoreHTTPSErrors: true });
|
||||
const page = await browser.newPage();
|
||||
await page.setBypassCSP(true);
|
||||
await page.goto("https://localhost:1234/hostedExplorer.html");
|
||||
|
||||
const results = await new AxePuppeteer(page).withTags(["wcag2a", "wcag2aa"]).analyze();
|
||||
if (results.violations && results.violations.length && results.violations.length > 0) {
|
||||
throw results.violations;
|
||||
}
|
||||
|
||||
await page.close();
|
||||
await browser.close();
|
||||
console.log(`Accessibility Check Passed!`);
|
||||
})().catch(err => {
|
||||
console.error(`Accessibility Check Failed: ${err.length} Errors`);
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
|
@ -18,7 +18,6 @@ function friendlyTime(date) {
|
|||
}
|
||||
}
|
||||
|
||||
// Deletes all SQL and Mongo databases created more than 20 minutes ago in the test runner accounts
|
||||
async function main() {
|
||||
const credentials = await msRestNodeAuth.loginWithServicePrincipalSecret(clientId, secret, tenantId);
|
||||
const client = new CosmosDBManagementClient(credentials, subscriptionId);
|
||||
|
@ -27,6 +26,7 @@ async function main() {
|
|||
if (account.kind === "MongoDB") {
|
||||
const mongoDatabases = await client.mongoDBResources.listMongoDBDatabases(resourceGroupName, account.name);
|
||||
for (const database of mongoDatabases) {
|
||||
// Unfortunately Mongo does not provide a timestamp in ARM. There is no way to tell how old the DB is other thn encoding it in the ID :(
|
||||
const timestamp = Number(database.name.split("-")[1]);
|
||||
if (timestamp && timestamp < thirtyMinutesAgo) {
|
||||
await client.mongoDBResources.deleteMongoDBDatabase(resourceGroupName, account.name, database.name);
|
||||
|
@ -35,10 +35,46 @@ async function main() {
|
|||
console.log(`SKIPPED: ${account.name} | ${database.name} | Age: ${friendlyTime(Date.now() - timestamp)}`);
|
||||
}
|
||||
}
|
||||
} else if (account.capabilities.find((c) => c.name === "EnableCassandra")) {
|
||||
const cassandraDatabases = await client.cassandraResources.listCassandraKeyspaces(
|
||||
resourceGroupName,
|
||||
account.name
|
||||
);
|
||||
for (const database of cassandraDatabases) {
|
||||
const timestamp = Number(database.resource._ts) * 1000;
|
||||
if (timestamp && timestamp < thirtyMinutesAgo) {
|
||||
await client.cassandraResources.deleteCassandraKeyspace(resourceGroupName, account.name, database.name);
|
||||
console.log(`DELETED: ${account.name} | ${database.name} | Age: ${friendlyTime(Date.now() - timestamp)}`);
|
||||
} else {
|
||||
console.log(`SKIPPED: ${account.name} | ${database.name} | Age: ${friendlyTime(Date.now() - timestamp)}`);
|
||||
}
|
||||
}
|
||||
} else if (account.capabilities.find((c) => c.name === "EnableTable")) {
|
||||
const tablesDatabase = await client.tableResources.listTables(resourceGroupName, account.name);
|
||||
for (const database of tablesDatabase) {
|
||||
const timestamp = Number(database.resource._ts) * 1000;
|
||||
if (timestamp && timestamp < thirtyMinutesAgo) {
|
||||
await client.tableResources.deleteTable(resourceGroupName, account.name, database.name);
|
||||
console.log(`DELETED: ${account.name} | ${database.name} | Age: ${friendlyTime(Date.now() - timestamp)}`);
|
||||
} else {
|
||||
console.log(`SKIPPED: ${account.name} | ${database.name} | Age: ${friendlyTime(Date.now() - timestamp)}`);
|
||||
}
|
||||
}
|
||||
} else if (account.capabilities.find((c) => c.name === "EnableGremlin")) {
|
||||
const graphDatabases = await client.gremlinResources.listGremlinDatabases(resourceGroupName, account.name);
|
||||
for (const database of graphDatabases) {
|
||||
const timestamp = Number(database.resource._ts) * 1000;
|
||||
if (timestamp && timestamp < thirtyMinutesAgo) {
|
||||
await client.gremlinResources.deleteGremlinDatabase(resourceGroupName, account.name, database.name);
|
||||
console.log(`DELETED: ${account.name} | ${database.name} | Age: ${friendlyTime(Date.now() - timestamp)}`);
|
||||
} else {
|
||||
console.log(`SKIPPED: ${account.name} | ${database.name} | Age: ${friendlyTime(Date.now() - timestamp)}`);
|
||||
}
|
||||
}
|
||||
} else if (account.kind === "GlobalDocumentDB") {
|
||||
const sqlDatabases = await client.sqlResources.listSqlDatabases(resourceGroupName, account.name);
|
||||
for (const database of sqlDatabases) {
|
||||
const timestamp = Number(database.name.split("-")[1]);
|
||||
const timestamp = Number(database.resource._ts) * 1000;
|
||||
if (timestamp && timestamp < thirtyMinutesAgo) {
|
||||
await client.sqlResources.deleteSqlDatabase(resourceGroupName, account.name, database.name);
|
||||
console.log(`DELETED: ${account.name} | ${database.name} | Age: ${friendlyTime(Date.now() - timestamp)}`);
|
||||
|
|
Loading…
Reference in New Issue