Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
7ac6a26
Temporarily re-enable key based auth for Mongo and Cassandra tests.
jawelton74 Dec 23, 2025
e75af41
Increase number of shards for playwright tests.
jawelton74 Dec 26, 2025
ef1e26f
Another small bump to test shard count.
jawelton74 Dec 26, 2025
74363dd
click global new... button then collection in playwright tests
Dec 26, 2025
4504942
get new table button
Dec 26, 2025
5a24db2
create and delete container for every individual scale test
Dec 26, 2025
d6a84af
for scale and settings, dont create sample data in container
Dec 27, 2025
e9f1229
run scale tests serially
Dec 29, 2025
de293d3
Merge branch 'master' of https://github.com/Azure/cosmos-explorer int…
Dec 29, 2025
9bdb995
refactor scale setup and tear down to be within each test
Dec 29, 2025
79e9f3a
record network traces
Dec 29, 2025
ff5ebda
record network calls on all retries
Dec 29, 2025
1736e24
when disposing of database during playwright test, refresh tree to re…
Dec 29, 2025
459b2c7
refresh tree before opening scale and settings
Dec 30, 2025
44de3ad
When opening scale and settings, refresh databases
Dec 30, 2025
eff7365
reload all databases before loading offers
Dec 30, 2025
1232596
increase time for change partition key request
Dec 30, 2025
2758e6a
increase time for change partition key request
Dec 30, 2025
87d5f84
refresh databases in test instead of product code
Dec 30, 2025
d235be6
when refreshing containers, open console window to check for status c…
Dec 31, 2025
e2e9cec
close notification console window after seeing desired log
Dec 31, 2025
644226d
create and delete a container for each individual test
Dec 31, 2025
7ddedf3
dont delete database after every test. leave it to the CI
Dec 31, 2025
7aa6243
Don't refresh databases when opening Scale+Settings and only delete d…
Dec 31, 2025
1f1ccc7
only open scale and settings at the beginning of each test suite
Jan 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 14 additions & 14 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,8 @@ jobs:
strategy:
fail-fast: false
matrix:
shardIndex: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
shardTotal: [16]
shardIndex: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
shardTotal: [20]
steps:
- uses: actions/checkout@v4
- name: Use Node.js 18.x
Expand Down Expand Up @@ -198,18 +198,18 @@ jobs:
GREMLIN_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://github-e2etests-gremlin.documents.azure.com/.default" -o tsv --query accessToken)
echo "::add-mask::$GREMLIN_TESTACCOUNT_TOKEN"
echo GREMLIN_TESTACCOUNT_TOKEN=$GREMLIN_TESTACCOUNT_TOKEN >> $GITHUB_ENV
CASSANDRA_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://github-e2etests-cassandra.documents.azure.com/.default" -o tsv --query accessToken)
echo "::add-mask::$CASSANDRA_TESTACCOUNT_TOKEN"
echo CASSANDRA_TESTACCOUNT_TOKEN=$CASSANDRA_TESTACCOUNT_TOKEN >> $GITHUB_ENV
MONGO_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://github-e2etests-mongo.documents.azure.com/.default" -o tsv --query accessToken)
echo "::add-mask::$MONGO_TESTACCOUNT_TOKEN"
echo MONGO_TESTACCOUNT_TOKEN=$MONGO_TESTACCOUNT_TOKEN >> $GITHUB_ENV
MONGO32_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://github-e2etests-mongo32.documents.azure.com/.default" -o tsv --query accessToken)
echo "::add-mask::$MONGO32_TESTACCOUNT_TOKEN"
echo MONGO32_TESTACCOUNT_TOKEN=$MONGO32_TESTACCOUNT_TOKEN >> $GITHUB_ENV
MONGO_READONLY_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://github-e2etests-mongo-readonly.documents.azure.com/.default" -o tsv --query accessToken)
echo "::add-mask::$MONGO_READONLY_TESTACCOUNT_TOKEN"
echo MONGO_READONLY_TESTACCOUNT_TOKEN=$MONGO_READONLY_TESTACCOUNT_TOKEN >> $GITHUB_ENV
# CASSANDRA_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://github-e2etests-cassandra.documents.azure.com/.default" -o tsv --query accessToken)
# echo "::add-mask::$CASSANDRA_TESTACCOUNT_TOKEN"
# echo CASSANDRA_TESTACCOUNT_TOKEN=$CASSANDRA_TESTACCOUNT_TOKEN >> $GITHUB_ENV
# MONGO_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://github-e2etests-mongo.documents.azure.com/.default" -o tsv --query accessToken)
# echo "::add-mask::$MONGO_TESTACCOUNT_TOKEN"
# echo MONGO_TESTACCOUNT_TOKEN=$MONGO_TESTACCOUNT_TOKEN >> $GITHUB_ENV
# MONGO32_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://github-e2etests-mongo32.documents.azure.com/.default" -o tsv --query accessToken)
# echo "::add-mask::$MONGO32_TESTACCOUNT_TOKEN"
# echo MONGO32_TESTACCOUNT_TOKEN=$MONGO32_TESTACCOUNT_TOKEN >> $GITHUB_ENV
# MONGO_READONLY_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://github-e2etests-mongo-readonly.documents.azure.com/.default" -o tsv --query accessToken)
# echo "::add-mask::$MONGO_READONLY_TESTACCOUNT_TOKEN"
# echo MONGO_READONLY_TESTACCOUNT_TOKEN=$MONGO_READONLY_TESTACCOUNT_TOKEN >> $GITHUB_ENV
- name: Run test shard ${{ matrix['shardIndex'] }} of ${{ matrix['shardTotal']}}
run: npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --workers=3
- name: Upload blob report to GitHub Actions Artifacts
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ export default defineConfig({
reporter: process.env.CI ? "blob" : "html",
timeout: 10 * 60 * 1000,
use: {
trace: "off",
video: "off",
trace: "on-all-retries",
video: "on-first-retry",
screenshot: "on",
testIdAttribute: "data-test",
contextOptions: {
Expand Down
11 changes: 6 additions & 5 deletions src/Explorer/Explorer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -437,13 +437,14 @@ export default class Explorer {
public onRefreshResourcesClick = async (): Promise<void> => {
if (isFabricMirroredKey()) {
scheduleRefreshFabricToken(true).then(() => this.refreshAllDatabases());
return;
} else {
await (userContext.authType === AuthType.ResourceToken
? this.refreshDatabaseForResourceToken()
: this.refreshAllDatabases());
await this.refreshNotebookList();
}

await (userContext.authType === AuthType.ResourceToken
? this.refreshDatabaseForResourceToken()
: this.refreshAllDatabases());
await this.refreshNotebookList();
logConsoleInfo("Successfully refreshed databases");
};

// Facade
Expand Down
3 changes: 3 additions & 0 deletions src/Explorer/Tree/Collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,9 @@ export default class Collection implements ViewModels.Collection {
public onSettingsClick = async (): Promise<void> => {
useSelectedNode.getState().setSelectedNode(this);
const throughputCap = userContext.databaseAccount?.properties.capacity?.totalThroughputLimit;
// // Refresh all databases in case they were deleted outside of user session
await this.container.onRefreshResourcesClick();

throughputCap && throughputCap !== -1 ? await useDatabases.getState().loadAllOffers() : await this.loadOffer();
this.selectedSubnodeKind(ViewModels.CollectionTabKind.Settings);
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
Expand Down
3 changes: 2 additions & 1 deletion test/cassandra/container.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ test("Cassandra keyspace and table CRUD", async ({ page }) => {

const explorer = await DataExplorer.open(page, TestAccount.Cassandra);

await explorer.globalCommandButton("New Table").click();
const newTableButton = await explorer.globalCommandButton("New Table");
await newTableButton.click();
await explorer.whilePanelOpen(
"Add Table",
async (panel, okButton) => {
Expand Down
41 changes: 38 additions & 3 deletions test/fx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -352,8 +352,9 @@ export class DataExplorer {
*
* There's only a single "primary" button, but we still require you to pass the label to confirm you're selecting the right button.
*/
globalCommandButton(label: string): Locator {
return this.frame.getByTestId("GlobalCommands").getByText(label);
async globalCommandButton(label: string): Promise<Locator> {
await this.frame.getByTestId("GlobalCommands").click();
return this.frame.getByRole("menuitem", { name: label });
}

/** Select the command bar button with the specified label */
Expand Down Expand Up @@ -459,17 +460,51 @@ export class DataExplorer {
const containerNode = await this.waitForContainerNode(context.database.id, context.container.id);
await containerNode.expand();

// // refresh tree to remove deleted database
// const consoleMessages = await this.getNotificationConsoleMessages();
// const refreshButton = this.frame.getByTestId("Sidebar/RefreshButton");
// await refreshButton.click();
// await expect(consoleMessages).toContainText("Successfully refreshed databases", {
// timeout: ONE_MINUTE_MS,
// });
// await this.collapseNotificationConsole();

const scaleAndSettingsButton = this.frame.getByTestId(
`TreeNode:${context.database.id}/${context.container.id}/Scale & Settings`,
);
await scaleAndSettingsButton.click();
}

/** Gets the console message element */
getConsoleMessage(): Locator {
getConsoleHeaderStatus(): Locator {
return this.frame.getByTestId("notification-console/header-status");
}

async expandNotificationConsole(): Promise<void> {
await this.setNotificationConsoleExpanded(true);
}

async collapseNotificationConsole(): Promise<void> {
await this.setNotificationConsoleExpanded(false);
}

async setNotificationConsoleExpanded(expanded: boolean): Promise<void> {
const notificationConsoleToggleButton = this.frame.getByTestId("NotificationConsole/ExpandCollapseButton");
const alt = await notificationConsoleToggleButton.locator("img").getAttribute("alt");

// When expanded, the icon says "Collapse icon"
if (expanded && alt === "Expand icon") {
await notificationConsoleToggleButton.click();
} else if (!expanded && alt === "Collapse icon") {
await notificationConsoleToggleButton.click();
}
}

async getNotificationConsoleMessages(): Promise<Locator> {
await this.setNotificationConsoleExpanded(true);
return this.frame.getByTestId("NotificationConsole/Contents");
}

async getDropdownItemByName(name: string, ariaLabel?: string): Promise<Locator> {
const dropdownItemsWrapper = this.frame.locator("div.ms-Dropdown-items");
if (ariaLabel) {
Expand Down
3 changes: 2 additions & 1 deletion test/gremlin/container.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ test("Gremlin graph CRUD", async ({ page }) => {
const explorer = await DataExplorer.open(page, TestAccount.Gremlin);

// Create new database and graph
await explorer.globalCommandButton("New Graph").click();
const newGraphButton = await explorer.globalCommandButton("New Graph");
await newGraphButton.click();
await explorer.whilePanelOpen(
"New Graph",
async (panel, okButton) => {
Expand Down
3 changes: 2 additions & 1 deletion test/mongo/container.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import { DataExplorer, TEST_AUTOSCALE_THROUGHPUT_RU, TestAccount, generateUnique

const explorer = await DataExplorer.open(page, accountType);

await explorer.globalCommandButton("New Collection").click();
const newCollectionButton = await explorer.globalCommandButton("New Collection");
await newCollectionButton.click();
await explorer.whilePanelOpen(
"New Collection",
async (panel, okButton) => {
Expand Down
3 changes: 2 additions & 1 deletion test/sql/container.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ test("SQL database and container CRUD", async ({ page }) => {

const explorer = await DataExplorer.open(page, TestAccount.SQL);

await explorer.globalCommandButton("New Container").click();
const newContainerButton = await explorer.globalCommandButton("New Container");
await newContainerButton.click();
await explorer.whilePanelOpen(
"New Container",
async (panel, okButton) => {
Expand Down
8 changes: 5 additions & 3 deletions test/sql/scaleAndSettings/changePartitionKey.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@ test.describe("Change Partition Key", () => {
await PartitionKeyTab.click();
});

test.afterAll("Delete Test Database", async () => {
await context?.dispose();
});
if (!process.env.CI) {
test.afterEach("Delete Test Database", async () => {
await context?.dispose();
});
}

test("Change partition key path", async () => {
await expect(explorer.frame.getByText("/partitionKey")).toBeVisible();
Expand Down
117 changes: 68 additions & 49 deletions test/sql/scaleAndSettings/scale.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,39 +9,38 @@ import {
} from "../../fx";
import { createTestSQLContainer, TestContainerContext } from "../../testData";

test.describe("Autoscale and Manual throughput", () => {
test.describe("Autoscale throughput", () => {
let context: TestContainerContext = null!;
let explorer: DataExplorer = null!;

test.beforeAll("Create Test Database", async () => {
context = await createTestSQLContainer({ includeTestData: true });
});

test.beforeEach("Open container settings", async ({ page }) => {
test.beforeAll("Create Test Database", async ({ browser }) => {
context = await createTestSQLContainer();
const page = await browser.newPage();
explorer = await DataExplorer.open(page, TestAccount.SQL);

// Click Scale & Settings and open Scale tab
await explorer.openScaleAndSettings(context);
const scaleTab = explorer.frame.getByTestId("settings-tab-header/ScaleTab");
await scaleTab.click();
});

test.afterAll("Delete Test Database", async () => {
await context?.dispose();
await switchManualToAutoscaleThroughput();
});

test("Update autoscale max throughput", async () => {
// By default the created container has manual throughput (Containers created via JS SDK v4.7.0 cannot be created with autoscale throughput)
await switchManualToAutoscaleThroughput();
if (!process.env.CI) {
test.afterAll("Delete Test Database", async () => {
await context?.dispose();
});
}

test("Update autoscale max throughput", async () => {
// Update autoscale max throughput
await getThroughputInput("autopilot").fill(TEST_AUTOSCALE_MAX_THROUGHPUT_RU_2K.toString());
await getThroughputInput(explorer, "autopilot").fill(TEST_AUTOSCALE_MAX_THROUGHPUT_RU_2K.toString());

// Save
await explorer.commandBarButton(CommandBarButton.Save).click();

// Read console message
await expect(explorer.getConsoleMessage()).toContainText(
await expect(explorer.getConsoleHeaderStatus()).toContainText(
`Successfully updated offer for collection ${context.container.id}`,
{
timeout: 2 * ONE_MINUTE_MS,
Expand All @@ -50,39 +49,72 @@ test.describe("Autoscale and Manual throughput", () => {
});

test("Update autoscale max throughput passed allowed limit", async () => {
// By default the created container has manual throughput (Containers created via JS SDK v4.7.0 cannot be created with autoscale throughput)
await switchManualToAutoscaleThroughput();

// Get soft allowed max throughput and remove commas
const softAllowedMaxThroughputString = await explorer.frame
.getByTestId("soft-allowed-maximum-throughput")
.innerText();
const softAllowedMaxThroughput = Number(softAllowedMaxThroughputString.replace(/,/g, ""));

// Try to set autoscale max throughput above allowed limit
await getThroughputInput("autopilot").fill((softAllowedMaxThroughput * 10).toString());
await getThroughputInput(explorer, "autopilot").fill((softAllowedMaxThroughput * 10).toString());
await expect(explorer.commandBarButton(CommandBarButton.Save)).toBeDisabled();
await expect(getThroughputInputErrorMessage("autopilot")).toContainText(
await expect(getThroughputInputErrorMessage(explorer, "autopilot")).toContainText(
"This update isn't possible because it would increase the total throughput",
);
});

test("Update autoscale max throughput with invalid increment", async () => {
// By default the created container has manual throughput (Containers created via JS SDK v4.7.0 cannot be created with autoscale throughput)
await switchManualToAutoscaleThroughput();

// Try to set autoscale max throughput with invalid increment
await getThroughputInput("autopilot").fill("1100");
await getThroughputInput(explorer, "autopilot").fill("1100");
await expect(explorer.commandBarButton(CommandBarButton.Save)).toBeDisabled();
await expect(getThroughputInputErrorMessage("autopilot")).toContainText(
await expect(getThroughputInputErrorMessage(explorer, "autopilot")).toContainText(
"Throughput value must be in increments of 1000",
);
});

const switchManualToAutoscaleThroughput = async (): Promise<void> => {
const autoscaleRadioButton = explorer.frame.getByText("Autoscale", { exact: true });
await autoscaleRadioButton.click();
await expect(explorer.commandBarButton(CommandBarButton.Save)).toBeEnabled();
await explorer.commandBarButton(CommandBarButton.Save).click();
await expect(explorer.getConsoleHeaderStatus()).toContainText(
`Successfully updated offer for collection ${context.container.id}`,
{
timeout: ONE_MINUTE_MS,
},
);
};
});

test.describe("Manual throughput", () => {
let context: TestContainerContext = null!;
let explorer: DataExplorer = null!;

test.beforeAll("Create Test Database & Open container settings", async ({ browser }) => {
context = await createTestSQLContainer();
const page = await browser.newPage();
explorer = await DataExplorer.open(page, TestAccount.SQL);

// Click Scale & Settings and open Scale tab
await explorer.openScaleAndSettings(context);
const scaleTab = explorer.frame.getByTestId("settings-tab-header/ScaleTab");
await scaleTab.click();
});

// test.beforeEach("Open container settings", async ({ page }) => {

// });

if (!process.env.CI) {
test.afterAll("Delete Test Database", async () => {
await context?.dispose();
});
}

test("Update manual throughput", async () => {
await getThroughputInput("manual").fill(TEST_MANUAL_THROUGHPUT_RU_2K.toString());
await getThroughputInput(explorer, "manual").fill(TEST_MANUAL_THROUGHPUT_RU_2K.toString());
await explorer.commandBarButton(CommandBarButton.Save).click();
await expect(explorer.getConsoleMessage()).toContainText(
await expect(explorer.getConsoleHeaderStatus()).toContainText(
`Successfully updated offer for collection ${context.container.id}`,
{
timeout: 2 * ONE_MINUTE_MS,
Expand All @@ -98,32 +130,19 @@ test.describe("Autoscale and Manual throughput", () => {
const softAllowedMaxThroughput = Number(softAllowedMaxThroughputString.replace(/,/g, ""));

// Try to set manual throughput above allowed limit
await getThroughputInput("manual").fill((softAllowedMaxThroughput * 10).toString());
await getThroughputInput(explorer, "manual").fill((softAllowedMaxThroughput * 10).toString());
await expect(explorer.commandBarButton(CommandBarButton.Save)).toBeDisabled();
await expect(getThroughputInputErrorMessage("manual")).toContainText(
await expect(getThroughputInputErrorMessage(explorer, "manual")).toContainText(
"This update isn't possible because it would increase the total throughput",
);
});
});

// Helper methods
const getThroughputInput = (type: "manual" | "autopilot"): Locator => {
return explorer.frame.getByTestId(`${type}-throughput-input`);
};
// Helper methods
const getThroughputInput = (explorer: DataExplorer, type: "manual" | "autopilot"): Locator => {
return explorer.frame.getByTestId(`${type}-throughput-input`);
};

const getThroughputInputErrorMessage = (type: "manual" | "autopilot"): Locator => {
return explorer.frame.getByTestId(`${type}-throughput-input-error`);
};

const switchManualToAutoscaleThroughput = async (): Promise<void> => {
const autoscaleRadioButton = explorer.frame.getByText("Autoscale", { exact: true });
await autoscaleRadioButton.click();
await expect(explorer.commandBarButton(CommandBarButton.Save)).toBeEnabled();
await explorer.commandBarButton(CommandBarButton.Save).click();
await expect(explorer.getConsoleMessage()).toContainText(
`Successfully updated offer for collection ${context.container.id}`,
{
timeout: ONE_MINUTE_MS,
},
);
};
});
const getThroughputInputErrorMessage = (explorer: DataExplorer, type: "manual" | "autopilot"): Locator => {
return explorer.frame.getByTestId(`${type}-throughput-input-error`);
};
Loading
Loading