mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-18 16:31:31 +00:00
Fix E2E tests. Add Playwright (#698)
This commit is contained in:
@@ -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;
|
||||
};
|
||||
27
test/notebooks/upload.spec.ts
Normal file
27
test/notebooks/upload.spec.ts
Normal file
@@ -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;
|
||||
}
|
||||
});
|
||||
});
|
||||
26
test/playwrightEnv.js
Normal file
26
test/playwrightEnv.js
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
11
test/utils/safeClick.ts
Normal file
11
test/utils/safeClick.ts
Normal file
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user