From 58b5caed7e604d28ec2a0b77d2bfbf82f558c773 Mon Sep 17 00:00:00 2001 From: Srinath Narayanan Date: Fri, 13 Nov 2020 14:27:27 -0800 Subject: [PATCH] Added Auth --- package-lock.json | 85 ++++- package.json | 3 +- src/Explorer/Explorer.ts | 1 - src/TestExplorer.ts | 326 +++++------------- src/TestExplorerParams.ts | 9 + src/testExplorer.html | 9 +- test/notebooks/notebookTestUtils.ts | 84 ++++- .../uploadOpenAndDeleteNotebook.spec.ts | 27 ++ .../uploadRunAndDeleteNotebook.spec.ts | 82 ----- test/utils/shared.ts | 2 - 10 files changed, 280 insertions(+), 348 deletions(-) create mode 100644 src/TestExplorerParams.ts create mode 100644 test/notebooks/uploadOpenAndDeleteNotebook.spec.ts delete mode 100644 test/notebooks/uploadRunAndDeleteNotebook.spec.ts diff --git a/package-lock.json b/package-lock.json index 0f4722727..ac59128f0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,23 @@ } } }, + "@azure/arm-cosmosdb": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/@azure/arm-cosmosdb/-/arm-cosmosdb-9.1.0.tgz", + "integrity": "sha512-ZHQTnBSjJ+TUAlXqfc1M23A0622gSSvYVd5gCqWHwG64e/R4zAySDDXcIi0bGYAUv/0nZzKHYulrgYpU+GnDjw==", + "requires": { + "@azure/ms-rest-azure-js": "^2.0.1", + "@azure/ms-rest-js": "^2.0.4", + "tslib": "^1.10.0" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } + }, "@azure/core-auth": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.1.3.tgz", @@ -200,6 +217,71 @@ } } }, + "@azure/ms-rest-azure-js": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@azure/ms-rest-azure-js/-/ms-rest-azure-js-2.0.1.tgz", + "integrity": "sha512-5e+A710O7gRFISoV4KI/ZyLQbKmjXxQZ1L8Z/sx7jSUQqmswjTnN4yyIZxs5JzfLVkobU0rXxbi5/LVzaI8QXQ==", + "requires": { + "@azure/ms-rest-js": "^2.0.4", + "tslib": "^1.10.0" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } + }, + "@azure/ms-rest-js": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@azure/ms-rest-js/-/ms-rest-js-2.1.0.tgz", + "integrity": "sha512-4BXLVImYRt+jcUmEJ5LUWglI8RBNVQndY6IcyvQ4U8O4kIXdmlRz3cJdA/RpXf5rKT38KOoTO2T6Z1f6Z1HDBg==", + "requires": { + "@types/node-fetch": "^2.3.7", + "@types/tunnel": "0.0.1", + "abort-controller": "^3.0.0", + "form-data": "^2.5.0", + "node-fetch": "^2.6.0", + "tough-cookie": "^3.0.1", + "tslib": "^1.10.0", + "tunnel": "0.0.6", + "uuid": "^3.3.2", + "xml2js": "^0.4.19" + }, + "dependencies": { + "form-data": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", + "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "tough-cookie": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz", + "integrity": "sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==", + "requires": { + "ip-regex": "^2.1.0", + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + } + } + }, "@babel/code-frame": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", @@ -11731,8 +11813,7 @@ "ip-regex": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", - "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", - "dev": true + "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=" }, "ipaddr.js": { "version": "1.9.1", diff --git a/package.json b/package.json index 836b518fb..d3c58e96f 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "description": "Cosmos Explorer", "main": "index.js", "dependencies": { + "@azure/arm-cosmosdb": "9.1.0", "@azure/cosmos": "3.9.0", "@azure/cosmos-language-service": "0.0.4", "@azure/identity": "1.1.0", @@ -188,7 +189,7 @@ "pack:fast": "node --max_old_space_size=10196 ./node_modules/webpack/bin/webpack.js --mode development --progress", "copyToConsumers": "node copyToConsumers", "test": "rimraf coverage && jest", - "test:e2e": "jest -c ./jest.config.e2e.js --detectOpenHandles test/notebooks/uploadRunAndDeleteNotebook.spec.ts", + "test:e2e": "jest -c ./jest.config.e2e.js --detectOpenHandles", "watch": "npm run start", "wait-for-server": "wait-on -t 240000 -i 5000 -v https-get://0.0.0.0:1234/", "build:ase": "gulp build:ase", diff --git a/src/Explorer/Explorer.ts b/src/Explorer/Explorer.ts index 94f9f506d..0cf235f56 100644 --- a/src/Explorer/Explorer.ts +++ b/src/Explorer/Explorer.ts @@ -2543,7 +2543,6 @@ export default class Explorer { const databaseAccountLocation = databaseAccount && databaseAccount.location.toLowerCase(); const disallowedLocationsUri = `${configContext.BACKEND_ENDPOINT}/api/disallowedLocations`; const authorizationHeader = getAuthorizationHeader(); - console.log("auth header:" + JSON.stringify(authorizationHeader)); try { const response = await fetch(disallowedLocationsUri, { method: "POST", diff --git a/src/TestExplorer.ts b/src/TestExplorer.ts index 79701856a..9395dfbcd 100644 --- a/src/TestExplorer.ts +++ b/src/TestExplorer.ts @@ -1,20 +1,75 @@ -import "./Shared/appInsights"; -import * as _ from "underscore"; import * as ko from "knockout"; import { MessageTypes } from "./Contracts/ExplorerContracts"; +import * as ViewModels from "./Contracts/ViewModels"; import "../less/hostedexplorer.less"; import "./Explorer/Menus/NavBar/MeControlComponent.less"; -import * as ViewModels from "./Contracts/ViewModels"; import { ClientSecretCredential } from "@azure/identity"; +import { CosmosDBManagementClient } from "@azure/arm-cosmosdb"; +import * as msRest from "@azure/ms-rest-js"; +import { DatabaseAccountsGetResponse } from "@azure/arm-cosmosdb/esm/models"; +import { TestExplorerParams } from "./TestExplorerParams"; + +class CustomSigner implements msRest.ServiceClientCredentials { + private token: string; + constructor(token: string) { + this.token = token; + } + + async signRequest(webResource: msRest.WebResourceLike): Promise { + webResource.headers.set("authorization", `bearer ${this.token}`); + return webResource; + } +} class TestExplorer { - public isButtonVisible: ko.Observable; + private notebooksTestRunnerApplicationId: string; + private notebooksTestRunnerClientId: string; + private notebooksTestRunnerClientSecret: string; + private notebooksAccountName: string; + private notebooksAccountKey: string; + private notebooksAccountSubscriptonId: string; + private notebooksAccountResourceGroup: string; constructor() { - this.isButtonVisible = ko.observable(true); + window.onload = () => { + this.initTestExplorer(); + }; window.addEventListener("message", this.handleMessage.bind(this), false); } + private parseUrlParams = (): void => { + window.location.search + .substr(1) + .split("&") + .forEach((item) => { + const tmp = item.split("="); + const value = decodeURIComponent(tmp[1]); + switch (tmp[0]) { + case TestExplorerParams.notebooksTestRunnerApplicationId: + this.notebooksTestRunnerApplicationId = value; + break; + case TestExplorerParams.notebooksTestRunnerClientId: + this.notebooksTestRunnerClientId = value; + break; + case TestExplorerParams.notebooksTestRunnerClientSecret: + this.notebooksTestRunnerClientSecret = value; + break; + case TestExplorerParams.notebooksAccountName: + this.notebooksAccountName = value; + break; + case TestExplorerParams.notebooksAccountKey: + this.notebooksAccountKey = value; + break; + case TestExplorerParams.notebooksAccountSubscriptonId: + this.notebooksAccountSubscriptonId = value; + break; + case TestExplorerParams.notebooksAccountResourceGroup: + this.notebooksAccountResourceGroup = value; + break; + } + }); + }; + private handleMessage(event: MessageEvent) { if (event.data.type === MessageTypes.InitTestExplorer || event.data.type === MessageTypes.HideConnectScreen) { this.sendMessageToExplorerFrame(event.data); @@ -22,253 +77,33 @@ class TestExplorer { } private async AADLogin(): Promise { - const tenantId = "72f988bf-86f1-41af-91ab-2d7cd011db47"; - const clientId = "fd8753b0-0707-4e32-84e9-2532af865fb4"; - const clientSecret = "xGT82g3sO4AJ.C~G6dii5LP~6-yCit9J-h"; - - const credentials = new ClientSecretCredential(tenantId, clientId, clientSecret); - + const credentials = new ClientSecretCredential( + this.notebooksTestRunnerApplicationId, + this.notebooksTestRunnerClientId, + this.notebooksTestRunnerClientSecret + ); const token = await credentials.getToken("https://management.core.windows.net/.default"); - return token.token; } - public async postMessage(): Promise { + private async getDatabaseAccount(token: string): Promise { + const client = new CosmosDBManagementClient(new CustomSigner(token), this.notebooksAccountSubscriptonId); + return await client.databaseAccounts.get(this.notebooksAccountResourceGroup, this.notebooksAccountName); + } + + private async initTestExplorer(): Promise { + this.parseUrlParams(); const token = await this.AADLogin(); + const databaseAccount = await this.getDatabaseAccount(token); + const content = { type: MessageTypes.InitTestExplorer, inputs: { - databaseAccount: { - id: - "/subscriptions/18f84a75-22a7-487c-a800-4e1bdad7779a/resourceGroups/srnara-cassandra-test/providers/Microsoft.DocumentDB/databaseAccounts/srnara-notebook", - name: "srnara-notebook", - location: "East US", - type: "Microsoft.DocumentDB/databaseAccounts", - kind: "GlobalDocumentDB", - tags: { defaultExperience: "Core (SQL)" }, - systemData: { createdAt: "2019-10-16T20:46:11.4096965Z" }, - properties: { - provisioningState: "Succeeded", - documentEndpoint: "https://srnara-notebook.documents.azure.com:443/", - publicNetworkAccess: "Enabled", - enableAutomaticFailover: false, - enableMultipleWriteLocations: true, - enablePartitionKeyMonitor: false, - isVirtualNetworkFilterEnabled: false, - virtualNetworkRules: [], - EnabledApiTypes: "Sql", - disableKeyBasedMetadataWriteAccess: false, - enableFreeTier: false, - enableAnalyticalStorage: true, - instanceId: "41978508-99b1-477d-9205-2d2f1ce7fc1a", - createMode: "Default", - databaseAccountOfferType: "Standard", - consistencyPolicy: { defaultConsistencyLevel: "Session", maxIntervalInSeconds: 5, maxStalenessPrefix: 100 }, - configurationOverrides: {}, - writeLocations: [ - { - id: "srnara-notebook-eastus", - locationName: "East US", - documentEndpoint: "https://srnara-notebook-eastus.documents.azure.com:443/", - provisioningState: "Succeeded", - failoverPriority: 0, - isZoneRedundant: false - } - ], - readLocations: [ - { - id: "srnara-notebook-eastus", - locationName: "East US", - documentEndpoint: "https://srnara-notebook-eastus.documents.azure.com:443/", - provisioningState: "Succeeded", - failoverPriority: 0, - isZoneRedundant: false - } - ], - locations: [ - { - id: "srnara-notebook-eastus", - locationName: "East US", - documentEndpoint: "https://srnara-notebook-eastus.documents.azure.com:443/", - provisioningState: "Succeeded", - failoverPriority: 0, - isZoneRedundant: false - } - ], - failoverPolicies: [{ id: "srnara-notebook-eastus", locationName: "East US", failoverPriority: 0 }], - cors: [], - capabilities: [], - ipRules: [], - backupPolicy: { - type: "Periodic", - periodicModeProperties: { backupIntervalInMinutes: 240, backupRetentionIntervalInHours: 8 } - } - } - }, - subscriptionId: "18f84a75-22a7-487c-a800-4e1bdad7779a", - resourceGroup: "srnara-cassandra-test", + databaseAccount: databaseAccount, + subscriptionId: this.notebooksAccountSubscriptonId, + resourceGroup: this.notebooksAccountResourceGroup, authorizationToken: `Bearer ${token}`, - features: { - cacheextensionapp: "false", - detailednetworktelemetry: "false", - logexternaldomainlinks: "true", - enableextensionpreviewstamp: "true", - gctelemetry: "false", - mereactblade: "true", - paralleltokens: "false", - prefetchbrowsequerymanifest: "false", - prefetchtokensinparallel: "true", - pretick: "false", - reactdatafetch: "false", - shellworker: "true", - shellworkerassets: "true", - shellworkerbrowseprereqs: "true", - shellworkersubs: "true", - simplebatch: "false", - storageperf1: "false", - storageperf2: "false", - earlymenucontentvm: "false", - bladefullrenderx: "false", - controlstelemetry: "true", - noeffectflight: "true", - advisornotificationdays: "30", - advisornotificationpercent: "100", - allserviceswithoverview: "true", - argsharedqueries: "true", - argsubscriptions: "true", - armviewer: "true", - asyncsearch: "true", - azureconsole: "true", - azurehome: "true", - columnchooserreact: "true", - contactinfo: "true", - custombingsearch: "true", - dashboardalphaapi: "true", - dashboardautorefresh: "true", - dashboardautorefreshinterval: "60", - dashboardfeedback: "true", - dashboardnewpinexperience: "true", - dashboardpreviewapi: "true", - dashboardrefresh: "true", - devsatsurvey: "true", - deploy2020: "true", - enableregionmove: "true", - enablestartswithmdm: "true", - enhancedprint: "true", - essentialsjsonview: "true", - freelancer: "true", - guidedtour: "true", - helpcontentwhatsnewenabled: "true", - hidefavoritestars: "true", - hostingservicesuffix: "mpac", - hubsresourceaccessfromconfig: "true", - internalonly: "nobanner", - iriscore: "true", - iriscorealt: "true", - iriscoresurfacename: "88000327", - irissurfacename: "AzurePortal_Notifications_Preview", - landalltohome: "true", - loggraphcallwitharmtoken: "true", - meazblade: "true", - mistendpoint: "https://mist.int.monitor.azure.com", - nojqueryeval: "true", - nopdlearlymenucontentbundles: "true", - npsintervaldays: "90", - npspercent: "2.4", - npsshowportaluri: "true", - policyawarecontrols: "true", - prefetchtokens: "true", - prewarmingtesting: "true", - reactviewendpointindex: "1", - reloadafterdays: "5", - serverfetchedevents: "true", - sessionvalidity: "true", - settingsportalinstance: "mpac", - shadowargcall: "true", - showbugreportlink: "true", - showhovercard: "true", - sidebarhamburgermode: "true", - singlesignout: "true", - subscreditcheck: "true", - tenants2020: "true", - tilegallerycuration: "true", - upgradefromtrialbutton: "true", - argbrowseviews: "true", - argforoldbrowse: "true", - argforrgoverview: "true", - argtagsfilter: "true", - artbrowse: "true", - automationtasks: "true", - browsecuration: "default", - browsedialogcompactpills: "true", - browsedialogpills: "true", - browsefilterstelemetry: "true", - bypasstokencacheforcustomsignin: "true", - cloudsimplereservations: "true", - contactabilitybycountry: "true", - cryptoapihash: "true", - dashboardfilters: "true", - dashboardfiltersaddbutton: "true", - devnps: "true", - devnpsintervaldays: "45", - devnpspercent: "50.0", - enableaeoemails: "false", - enablee2emonitoring: "true", - enablelocationchange: "true", - experimentation: "false", - failajaxonnulltoken: "true", - fastencode: "true", - feedback: "true", - feedbackwithsupport: "true", - fullscreenblades: "true", - hidemodalsonsmallscreens: "true", - hidemodalswhendeeplinked: "true", - irismessagelimit: "1", - isworkbooksavailable: "true", - migratetomsal: "true", - mspexpert: "true", - mspfilter: "true", - mspinfo: "true", - newresourceapi: "true", - newsupportblade: "true", - nps: "true", - outagebanner: "true", - portalpolling: "true", - preact: "true", - preferredusername: "true", - prefetchdrafttoken: "true", - prefetchrecents: "true", - providers2019: "true", - pushtokens: "true", - removesubsdropdownlimit: "true", - reservationsinbrowse: "true", - reservehozscroll: "true", - resourcehealth: "true", - savedeploymentnotification: "true", - seetemplate: "true", - serveravatar: "true", - showpostcreatefeedbackoption: "true", - showservicehealthalerts: "true", - showworkflowappkindbrowse: "true", - supplementalbatchsize: "20", - tenantscoperedirect: "true", - tokencaching: "true", - usealertsv2blade: "true", - usemsallogin: "true", - zerosubsexperience: "true", - regionsegments: "true", - allservicesweave: "false", - bundlingkind: "DefaultPartitioner", - confighash: "CGZNcAynkOLM", - env: "ms", - l: "en.en-us", - pageversion: "6.659.0.25051.201105-0922", - prefetchhome: "false", - prewarmie: "false", - weaveblade: "true", - dataexplorersource: "https://localhost:1234/explorer.html", - experimentationflights: "settingsv2;mongoindexeditor" - }, + features: {}, hasWriteAccess: true, csmEndpoint: "https://management.azure.com", dnsSuffix: "documents.azure.com", @@ -278,7 +113,7 @@ class TestExplorer { quotaId: "Internal_2014-09-01", addCollectionDefaultFlight: "2", isTryCosmosDBSubscription: false, - masterKey: "jB16xFppH34oIsrxhKytgqlGdq4n3UcHAD9J20jNosrOAzDKfAcvM1kfeBM49ccFxjpFW85Du2ISvrjdl7i4fg==", + masterKey: this.notebooksAccountKey, loadDatabaseAccountTimestamp: 1604663109836, dataExplorerVersion: "1.0.1", sharedThroughputMinimum: 400, @@ -297,10 +132,9 @@ class TestExplorer { type: MessageTypes.HideConnectScreen }; window.postMessage(hideConnectContent, window.location.href); - this.isButtonVisible(false); } - private sendMessageToExplorerFrame(data: any): void { + private sendMessageToExplorerFrame(data: unknown): void { const explorerFrame = document.getElementById("explorerMenu") as HTMLIFrameElement; explorerFrame && explorerFrame.contentDocument && diff --git a/src/TestExplorerParams.ts b/src/TestExplorerParams.ts new file mode 100644 index 000000000..b7b705e14 --- /dev/null +++ b/src/TestExplorerParams.ts @@ -0,0 +1,9 @@ +export enum TestExplorerParams { + notebooksTestRunnerApplicationId = "notebooksTestRunnerApplicationId", + notebooksTestRunnerClientId = "notebooksTestRunnerClientId", + notebooksTestRunnerClientSecret = "notebooksTestRunnerClientSecret", + notebooksAccountName = "notebooksAccountName", + notebooksAccountKey = "notebooksAccountKey", + notebooksAccountSubscriptonId = "notebooksAccountSubscriptonId", + notebooksAccountResourceGroup = "notebooksAccountResourceGroup" +} diff --git a/src/testExplorer.html b/src/testExplorer.html index d0ab0e73c..2da273bff 100644 --- a/src/testExplorer.html +++ b/src/testExplorer.html @@ -9,14 +9,7 @@ - - diff --git a/test/notebooks/notebookTestUtils.ts b/test/notebooks/notebookTestUtils.ts index 4472394a5..0e6227091 100644 --- a/test/notebooks/notebookTestUtils.ts +++ b/test/notebooks/notebookTestUtils.ts @@ -1,18 +1,90 @@ -import { Frame } from "puppeteer"; +import { ElementHandle, Frame } from "puppeteer"; +import { TestExplorerParams } from "../../src/TestExplorerParams"; + +export const NOTEBOOK_OPERATION_DELAY = 5000; +export const RENDER_DELAY = 1000; let testExplorerFrame: Frame; -export async function getTestExplorerFrame(): Promise { +export const getTestExplorerFrame = async (): Promise => { if (testExplorerFrame) { return testExplorerFrame; } - const prodUrl = "https://localhost:1234/testExplorer.html"; + const notebooksTestRunnerApplicationId = process.env.NOTEBOOKS_TEST_RUNNER_TENANT_ID; + const notebooksTestRunnerClientId = process.env.NOTEBOOKS_TEST_RUNNER_CLIENT_ID; + const notebooksTestRunnerClientSecret = process.env.NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET; + const notebooksAccountName = process.env.NOTEBOOKS_ACCOUNT_NAME; + const notebooksAccountKey = process.env.NOTEBOOKS_ACCOUNT_KEY; + const notebooksAccountSubscriptonId = process.env.NOTEBOOKS_ACCOUNT_SUBSCRIPTION_ID; + const notebooksAccountResourceGroup = process.env.NOTEBOOKS_ACCOUNT_RESOURCE_GROUP; + + const prodUrl = `https://localhost:1234/testExplorer.html? +${TestExplorerParams.notebooksTestRunnerApplicationId}=${encodeURI(notebooksTestRunnerApplicationId)}& +${TestExplorerParams.notebooksTestRunnerClientId}=${encodeURI(notebooksTestRunnerClientId)}& +${TestExplorerParams.notebooksTestRunnerClientSecret}=${encodeURI(notebooksTestRunnerClientSecret)}& +${TestExplorerParams.notebooksAccountName}=${encodeURI(notebooksAccountName)}& +${TestExplorerParams.notebooksAccountKey}=${encodeURI(notebooksAccountKey)}& +${TestExplorerParams.notebooksAccountSubscriptonId}=${encodeURI(notebooksAccountSubscriptonId)}& +${TestExplorerParams.notebooksAccountResourceGroup}=${encodeURI(notebooksAccountResourceGroup)}`; + await page.goto(prodUrl); - const buttonHandle = await page.waitForSelector("button"); - buttonHandle.click(); const handle = await page.waitForSelector("iframe"); testExplorerFrame = await handle.contentFrame(); await testExplorerFrame.waitForSelector(".galleryHeader"); return testExplorerFrame; -} +}; + +export const uploadNotebook = async (frame: Frame, uploadNotebookPath: string): Promise => { + 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); +}; + +export const getNotebookNode = async (frame: Frame, uploadNotebookName: string): Promise> => { + const notebookResourceTree = await frame.waitForSelector(".notebookResourceTree"); + let currentNotebookNode: ElementHandle; + + 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; +}; + +export const deleteNotebook = async (frame: Frame, notebookNodeToDelete: ElementHandle): Promise => { + const ellipses = await notebookNodeToDelete.$(".treeMenuEllipsis"); + await ellipses.click(); + + await frame.waitFor(RENDER_DELAY); + + const menuItems = await frame.$$(".ms-ContextualMenu-item"); + await menuItems[1].click(); + + const deleteAcceptButton = await frame.waitForSelector(".ms-Dialog-action"); + await deleteAcceptButton.click(); + await frame.waitFor(NOTEBOOK_OPERATION_DELAY); +}; diff --git a/test/notebooks/uploadOpenAndDeleteNotebook.spec.ts b/test/notebooks/uploadOpenAndDeleteNotebook.spec.ts new file mode 100644 index 000000000..f80795547 --- /dev/null +++ b/test/notebooks/uploadOpenAndDeleteNotebook.spec.ts @@ -0,0 +1,27 @@ +import "expect-puppeteer"; +import { deleteNotebook, getNotebookNode, getTestExplorerFrame, uploadNotebook } from "./notebookTestUtils"; +import * as path from "path"; + +jest.setTimeout(300000); + +describe("Notebook UI tests", () => { + it("Upload, Open and Delete Notebook", async () => { + const frame = await getTestExplorerFrame(); + const uploadNotebookName = "GettingStarted.ipynb"; + const uploadNotebookPath = path.join(__dirname, "testNotebooks", uploadNotebookName); + + await uploadNotebook(frame, uploadNotebookPath); + const uploadedNotebookNode = await getNotebookNode(frame, uploadNotebookName); + + await uploadedNotebookNode.click(); + await frame.waitForSelector(".tabNavText"); + const tabTitle = await frame.$eval(".tabNavText", element => element.textContent); + expect(tabTitle).toEqual(uploadNotebookName); + const closeIcon = await frame.waitForSelector(".close-Icon"); + await closeIcon.click(); + + await deleteNotebook(frame, uploadedNotebookNode); + const deletedNotebookNode = await getNotebookNode(frame, uploadNotebookName); + expect(deletedNotebookNode).toBeUndefined(); + }); +}); diff --git a/test/notebooks/uploadRunAndDeleteNotebook.spec.ts b/test/notebooks/uploadRunAndDeleteNotebook.spec.ts deleted file mode 100644 index dd1628e9c..000000000 --- a/test/notebooks/uploadRunAndDeleteNotebook.spec.ts +++ /dev/null @@ -1,82 +0,0 @@ -import "expect-puppeteer"; -import { ElementHandle } from "puppeteer"; -import { getTestExplorerFrame } from "./notebookTestUtils"; -import * as path from "path" - -jest.setTimeout(300000); -const NOTEBOOK_OPERATION_DELAY = 2500; -const RENDER_DELAY = 1000; - -describe("sample", () => { - it("portal login", async () => { - const frame = await getTestExplorerFrame(); - - const notebookResourceTree = await frame.waitForSelector(".notebookResourceTree"); - const uploadNotebookPath = "C:/Users/srnara/Downloads/GettingStarted.ipynb"; - const uploadNotebookName = path.basename(uploadNotebookPath); - - const treeNodeHeadersBeforeUpload = await notebookResourceTree.$$(".treeNodeHeader"); - - let ellipses = await treeNodeHeadersBeforeUpload[2].$("button"); - await ellipses.click(); - - await frame.waitFor(RENDER_DELAY); - - let 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); - - let uploadedNotebookNode: ElementHandle; - const treeNodeHeadersAfterUpload = await notebookResourceTree.$$(".treeNodeHeader"); - for (var i = 1; i < treeNodeHeadersAfterUpload.length; i++) { - uploadedNotebookNode = treeNodeHeadersAfterUpload[i]; - const nodeLabel = await uploadedNotebookNode.$eval(".nodeLabel", element => element.textContent); - if (nodeLabel === uploadNotebookName) { - break; - } - } - - await uploadedNotebookNode.click(); - await frame.waitForSelector(".tabNavText"); - const tabTitle = await frame.$eval(".tabNavText", element => element.textContent); - expect(tabTitle).toEqual(uploadNotebookName); - - const closeIcon = await frame.waitForSelector(".close-Icon"); - await closeIcon.click(); - - ellipses = await uploadedNotebookNode.$(".treeMenuEllipsis"); - await ellipses.click(); - - await frame.waitFor(RENDER_DELAY); - - menuItems = await frame.$$(".ms-ContextualMenu-item"); - await menuItems[1].click(); - - const deleteAcceptButton = await frame.waitForSelector(".ms-Dialog-action"); - await deleteAcceptButton.click(); - await frame.waitFor(NOTEBOOK_OPERATION_DELAY); - - let index: number; - let deletedNotebookNode: ElementHandle; - const treeNodeHeadersAfterDelete = await notebookResourceTree.$$(".treeNodeHeader"); - for (index = 1; index < treeNodeHeadersAfterDelete.length; index++) { - deletedNotebookNode = treeNodeHeadersAfterDelete[index]; - const nodeLabel = await deletedNotebookNode.$eval(".nodeLabel", element => element.textContent); - if (nodeLabel === uploadNotebookName) { - break; - } - } - - expect(index).toEqual(treeNodeHeadersAfterDelete.length); - }); -}); diff --git a/test/utils/shared.ts b/test/utils/shared.ts index dc71e17c1..458899700 100644 --- a/test/utils/shared.ts +++ b/test/utils/shared.ts @@ -1,8 +1,6 @@ import crypto from "crypto"; import { Frame } from "puppeteer"; -let testExplorerFrame: Frame; - export async function login(connectionString: string): Promise { const prodUrl = "https://localhost:1234/hostedExplorer.html"; page.goto(prodUrl);