Add playwright tests (#2274)

* Add playwright tests for Autoscale/Manual Throughpout and TTL

* fix unit tests and lint

* fix unit tests

* fix tests

* fix autoscale selector

* changed throughput above limit

---------

Co-authored-by: Asier Isayas <aisayas@microsoft.com>
This commit is contained in:
asier-isayas
2025-12-10 14:02:31 -05:00
committed by GitHub
parent 5b7d1a74af
commit d67c1a0464
13 changed files with 315 additions and 17 deletions

View File

@@ -1482,6 +1482,9 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
itemKey: SettingsV2TabTypes[tab.tab], itemKey: SettingsV2TabTypes[tab.tab],
style: { marginTop: 20 }, style: { marginTop: 20 },
headerText: getTabTitle(tab.tab), headerText: getTabTitle(tab.tab),
headerButtonProps: {
"data-test": `settings-tab-header/${SettingsV2TabTypes[tab.tab]}`,
},
}; };
return ( return (

View File

@@ -127,9 +127,9 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
}; };
private ttlChoiceGroupOptions: IChoiceGroupOption[] = [ private ttlChoiceGroupOptions: IChoiceGroupOption[] = [
{ key: TtlType.Off, text: "Off" }, { key: TtlType.Off, text: "Off", ariaLabel: "ttl-off-option" },
{ key: TtlType.OnNoDefault, text: "On (no default)" }, { key: TtlType.OnNoDefault, text: "On (no default)", ariaLabel: "ttl-on-no-default-option" },
{ key: TtlType.On, text: "On" }, { key: TtlType.On, text: "On", ariaLabel: "ttl-on-option" },
]; ];
public getTtlValue = (value: string): TtlType => { public getTtlValue = (value: string): TtlType => {
@@ -223,6 +223,7 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
onChange={this.onTimeToLiveSecondsChange} onChange={this.onTimeToLiveSecondsChange}
suffix="second(s)" suffix="second(s)"
ariaLabel={`Time to live in seconds`} ariaLabel={`Time to live in seconds`}
data-test="ttl-input"
/> />
)} )}
</Stack> </Stack>

View File

@@ -503,7 +503,9 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
<span style={{ float: "left", transform: "translateX(-50%)" }}> <span style={{ float: "left", transform: "translateX(-50%)" }}>
{this.props.instantMaximumThroughput.toLocaleString()} {this.props.instantMaximumThroughput.toLocaleString()}
</span> </span>
<span style={{ float: "right" }}>{this.props.softAllowedMaximumThroughput.toLocaleString()}</span> <span style={{ float: "right" }} data-test="soft-allowed-maximum-throughput">
{this.props.softAllowedMaximumThroughput.toLocaleString()}
</span>
</Stack.Item> </Stack.Item>
</Stack> </Stack>
<ProgressIndicator <ProgressIndicator
@@ -626,11 +628,12 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
min={autoPilotThroughput1K} min={autoPilotThroughput1K}
onGetErrorMessage={(value: string) => { onGetErrorMessage={(value: string) => {
const sanitizedValue = getSanitizedInputValue(value); const sanitizedValue = getSanitizedInputValue(value);
return sanitizedValue % 1000 const errorMessage: string =
? "Throughput value must be in increments of 1000" sanitizedValue % 1000 ? "Throughput value must be in increments of 1000" : this.props.throughputError;
: this.props.throughputError; return <span data-test="autopilot-throughput-input-error">{errorMessage}</span>;
}} }}
validateOnLoad={false} validateOnLoad={false}
data-test="autopilot-throughput-input"
/> />
</Stack> </Stack>
</Stack> </Stack>
@@ -650,7 +653,10 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
} }
onChange={this.onThroughputChange} onChange={this.onThroughputChange}
min={this.props.minimum} min={this.props.minimum}
errorMessage={this.props.throughputError} onGetErrorMessage={() => {
return <span data-test="manual-throughput-input-error">{this.props.throughputError}</span>;
}}
data-test="manual-throughput-input"
/> />
)} )}
</> </>

View File

@@ -273,6 +273,7 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
/> />
</Stack> </Stack>
<StyledTextFieldBase <StyledTextFieldBase
data-test="autopilot-throughput-input"
disabled={true} disabled={true}
id="autopilotInput" id="autopilotInput"
key="auto pilot throughput input" key="auto pilot throughput input"
@@ -333,6 +334,7 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
5,000 5,000
</span> </span>
<span <span
data-test="soft-allowed-maximum-throughput"
style={ style={
{ {
"float": "right", "float": "right",
@@ -752,11 +754,13 @@ exports[`ThroughputInputAutoPilotV3Component spendAck checkbox visible 1`] = `
} }
> >
<StyledTextFieldBase <StyledTextFieldBase
data-test="manual-throughput-input"
disabled={false} disabled={false}
id="throughputInput" id="throughputInput"
key="provisioned throughput input" key="provisioned throughput input"
min={10000} min={10000}
onChange={[Function]} onChange={[Function]}
onGetErrorMessage={[Function]}
required={true} required={true}
step={100} step={100}
styles={ styles={
@@ -811,6 +815,7 @@ exports[`ThroughputInputAutoPilotV3Component spendAck checkbox visible 1`] = `
5,000 5,000
</span> </span>
<span <span
data-test="soft-allowed-maximum-throughput"
style={ style={
{ {
"float": "right", "float": "right",
@@ -1206,11 +1211,13 @@ exports[`ThroughputInputAutoPilotV3Component throughput input visible 1`] = `
} }
> >
<StyledTextFieldBase <StyledTextFieldBase
data-test="manual-throughput-input"
disabled={false} disabled={false}
id="throughputInput" id="throughputInput"
key="provisioned throughput input" key="provisioned throughput input"
min={10000} min={10000}
onChange={[Function]} onChange={[Function]}
onGetErrorMessage={[Function]}
required={true} required={true}
step={100} step={100}
styles={ styles={
@@ -1265,6 +1272,7 @@ exports[`ThroughputInputAutoPilotV3Component throughput input visible 1`] = `
5,000 5,000
</span> </span>
<span <span
data-test="soft-allowed-maximum-throughput"
style={ style={
{ {
"float": "right", "float": "right",

View File

@@ -22,14 +22,17 @@ exports[`SubSettingsComponent analyticalTimeToLive hidden 1`] = `
options={ options={
[ [
{ {
"ariaLabel": "ttl-off-option",
"key": "off", "key": "off",
"text": "Off", "text": "Off",
}, },
{ {
"ariaLabel": "ttl-on-no-default-option",
"key": "on-nodefault", "key": "on-nodefault",
"text": "On (no default)", "text": "On (no default)",
}, },
{ {
"ariaLabel": "ttl-on-option",
"key": "on", "key": "on",
"text": "On", "text": "On",
}, },
@@ -63,6 +66,7 @@ exports[`SubSettingsComponent analyticalTimeToLive hidden 1`] = `
/> />
<StyledTextFieldBase <StyledTextFieldBase
ariaLabel="Time to live in seconds" ariaLabel="Time to live in seconds"
data-test="ttl-input"
id="timeToLiveSeconds" id="timeToLiveSeconds"
max={2147483647} max={2147483647}
min={1} min={1}
@@ -284,14 +288,17 @@ exports[`SubSettingsComponent analyticalTimeToLiveSeconds hidden 1`] = `
options={ options={
[ [
{ {
"ariaLabel": "ttl-off-option",
"key": "off", "key": "off",
"text": "Off", "text": "Off",
}, },
{ {
"ariaLabel": "ttl-on-no-default-option",
"key": "on-nodefault", "key": "on-nodefault",
"text": "On (no default)", "text": "On (no default)",
}, },
{ {
"ariaLabel": "ttl-on-option",
"key": "on", "key": "on",
"text": "On", "text": "On",
}, },
@@ -325,6 +332,7 @@ exports[`SubSettingsComponent analyticalTimeToLiveSeconds hidden 1`] = `
/> />
<StyledTextFieldBase <StyledTextFieldBase
ariaLabel="Time to live in seconds" ariaLabel="Time to live in seconds"
data-test="ttl-input"
id="timeToLiveSeconds" id="timeToLiveSeconds"
max={2147483647} max={2147483647}
min={1} min={1}
@@ -601,14 +609,17 @@ exports[`SubSettingsComponent changeFeedPolicy hidden 1`] = `
options={ options={
[ [
{ {
"ariaLabel": "ttl-off-option",
"key": "off", "key": "off",
"text": "Off", "text": "Off",
}, },
{ {
"ariaLabel": "ttl-on-no-default-option",
"key": "on-nodefault", "key": "on-nodefault",
"text": "On (no default)", "text": "On (no default)",
}, },
{ {
"ariaLabel": "ttl-on-option",
"key": "on", "key": "on",
"text": "On", "text": "On",
}, },
@@ -642,6 +653,7 @@ exports[`SubSettingsComponent changeFeedPolicy hidden 1`] = `
/> />
<StyledTextFieldBase <StyledTextFieldBase
ariaLabel="Time to live in seconds" ariaLabel="Time to live in seconds"
data-test="ttl-input"
id="timeToLiveSeconds" id="timeToLiveSeconds"
max={2147483647} max={2147483647}
min={1} min={1}
@@ -878,14 +890,17 @@ exports[`SubSettingsComponent renders 1`] = `
options={ options={
[ [
{ {
"ariaLabel": "ttl-off-option",
"key": "off", "key": "off",
"text": "Off", "text": "Off",
}, },
{ {
"ariaLabel": "ttl-on-no-default-option",
"key": "on-nodefault", "key": "on-nodefault",
"text": "On (no default)", "text": "On (no default)",
}, },
{ {
"ariaLabel": "ttl-on-option",
"key": "on", "key": "on",
"text": "On", "text": "On",
}, },
@@ -919,6 +934,7 @@ exports[`SubSettingsComponent renders 1`] = `
/> />
<StyledTextFieldBase <StyledTextFieldBase
ariaLabel="Time to live in seconds" ariaLabel="Time to live in seconds"
data-test="ttl-input"
id="timeToLiveSeconds" id="timeToLiveSeconds"
max={2147483647} max={2147483647}
min={1} min={1}
@@ -1220,14 +1236,17 @@ exports[`SubSettingsComponent timeToLiveSeconds hidden 1`] = `
options={ options={
[ [
{ {
"ariaLabel": "ttl-off-option",
"key": "off", "key": "off",
"text": "Off", "text": "Off",
}, },
{ {
"ariaLabel": "ttl-on-no-default-option",
"key": "on-nodefault", "key": "on-nodefault",
"text": "On (no default)", "text": "On (no default)",
}, },
{ {
"ariaLabel": "ttl-on-option",
"key": "on", "key": "on",
"text": "On", "text": "On",
}, },

View File

@@ -12,6 +12,11 @@ exports[`SettingsComponent renders 1`] = `
selectedKey="ScaleTab" selectedKey="ScaleTab"
> >
<PivotItem <PivotItem
headerButtonProps={
{
"data-test": "settings-tab-header/ScaleTab",
}
}
headerText="Scale" headerText="Scale"
itemKey="ScaleTab" itemKey="ScaleTab"
key="ScaleTab" key="ScaleTab"
@@ -102,6 +107,11 @@ exports[`SettingsComponent renders 1`] = `
/> />
</PivotItem> </PivotItem>
<PivotItem <PivotItem
headerButtonProps={
{
"data-test": "settings-tab-header/SubSettingsTab",
}
}
headerText="Settings" headerText="Settings"
itemKey="SubSettingsTab" itemKey="SubSettingsTab"
key="SubSettingsTab" key="SubSettingsTab"
@@ -201,6 +211,11 @@ exports[`SettingsComponent renders 1`] = `
/> />
</PivotItem> </PivotItem>
<PivotItem <PivotItem
headerButtonProps={
{
"data-test": "settings-tab-header/ContainerVectorPolicyTab",
}
}
headerText="Container Policies" headerText="Container Policies"
itemKey="ContainerVectorPolicyTab" itemKey="ContainerVectorPolicyTab"
key="ContainerVectorPolicyTab" key="ContainerVectorPolicyTab"
@@ -227,6 +242,11 @@ exports[`SettingsComponent renders 1`] = `
/> />
</PivotItem> </PivotItem>
<PivotItem <PivotItem
headerButtonProps={
{
"data-test": "settings-tab-header/IndexingPolicyTab",
}
}
headerText="Indexing Policy" headerText="Indexing Policy"
itemKey="IndexingPolicyTab" itemKey="IndexingPolicyTab"
key="IndexingPolicyTab" key="IndexingPolicyTab"
@@ -263,6 +283,11 @@ exports[`SettingsComponent renders 1`] = `
/> />
</PivotItem> </PivotItem>
<PivotItem <PivotItem
headerButtonProps={
{
"data-test": "settings-tab-header/PartitionKeyTab",
}
}
headerText="Partition Keys (preview)" headerText="Partition Keys (preview)"
itemKey="PartitionKeyTab" itemKey="PartitionKeyTab"
key="PartitionKeyTab" key="PartitionKeyTab"
@@ -370,6 +395,11 @@ exports[`SettingsComponent renders 1`] = `
/> />
</PivotItem> </PivotItem>
<PivotItem <PivotItem
headerButtonProps={
{
"data-test": "settings-tab-header/ComputedPropertiesTab",
}
}
headerText="Computed Properties" headerText="Computed Properties"
itemKey="ComputedPropertiesTab" itemKey="ComputedPropertiesTab"
key="ComputedPropertiesTab" key="ComputedPropertiesTab"
@@ -404,6 +434,11 @@ exports[`SettingsComponent renders 1`] = `
/> />
</PivotItem> </PivotItem>
<PivotItem <PivotItem
headerButtonProps={
{
"data-test": "settings-tab-header/GlobalSecondaryIndexTab",
}
}
headerText="Global Secondary Index (Preview)" headerText="Global Secondary Index (Preview)"
itemKey="GlobalSecondaryIndexTab" itemKey="GlobalSecondaryIndexTab"
key="GlobalSecondaryIndexTab" key="GlobalSecondaryIndexTab"

View File

@@ -127,7 +127,7 @@ export class NotificationConsoleComponent extends React.Component<
</span> </span>
</span> </span>
<span className="consoleSplitter" /> <span className="consoleSplitter" />
<span className="headerStatus"> <span className="headerStatus" data-test="notification-console/header-status">
<span className="headerStatusEllipsis" aria-live="assertive" aria-atomic="true"> <span className="headerStatusEllipsis" aria-live="assertive" aria-atomic="true">
{this.state.headerStatus} {this.state.headerStatus}
</span> </span>

View File

@@ -78,6 +78,7 @@ exports[`NotificationConsoleComponent renders the console 1`] = `
/> />
<span <span
className="headerStatus" className="headerStatus"
data-test="notification-console/header-status"
> >
<span <span
aria-atomic="true" aria-atomic="true"
@@ -261,6 +262,7 @@ exports[`NotificationConsoleComponent renders the console 2`] = `
/> />
<span <span
className="headerStatus" className="headerStatus"
data-test="notification-console/header-status"
> >
<span <span
aria-atomic="true" aria-atomic="true"

View File

@@ -40,13 +40,13 @@ To use this script, there are a few prerequisites that must be done at least onc
5. Ensure you have a Resource Group _ready_ to deploy into, the deploy script requires an existing resource group. This resource group should be named `[username]-e2e-testing`, where `[username]` is your Windows username, (**Microsoft employees:** This should be your alias). The easiest way to do this is by running the `create-resource-group.ps1` script, specifying the Subscription (Name or ID) and Location in which you want to create the Resource Group. For example: 5. Ensure you have a Resource Group _ready_ to deploy into, the deploy script requires an existing resource group. This resource group should be named `[username]-e2e-testing`, where `[username]` is your Windows username, (**Microsoft employees:** This should be your alias). The easiest way to do this is by running the `create-resource-group.ps1` script, specifying the Subscription (Name or ID) and Location in which you want to create the Resource Group. For example:
```powershell ```powershell
.\test\resources\create-resource-group.ps1 -SubscriptionName "My Subscription" -Location "West US 3" .\test\resources\create-resource-group.ps1 -SubscriptionId "My Subscription Id" -Location "West US 3"
``` ```
Then, whenever you want to create/update the resources, you can run the `deploy.ps1` script in the `resources` directory. As long as you're using the default naming convention (`[username]-e2e-testing`), you just need to specify the Subscription. For example: Then, whenever you want to create/update the resources, you can run the `deploy.ps1` script in the `resources` directory. As long as you're using the default naming convention (`[username]-e2e-testing`), you just need to specify the Subscription. For example:
```powershell ```powershell
.\test\resources\deploy.ps1 -SubscriptionName "My Subscription" .\test\resources\deploy.ps1 -Subscription "My Subscription"
``` ```
You'll get a confirmation prompt before anything is deployed: You'll get a confirmation prompt before anything is deployed:

View File

@@ -1,6 +1,7 @@
import { DefaultAzureCredential } from "@azure/identity"; import { DefaultAzureCredential } from "@azure/identity";
import { Frame, Locator, Page, expect } from "@playwright/test"; import { Frame, Locator, Page, expect } from "@playwright/test";
import crypto from "crypto"; import crypto from "crypto";
import { TestContainerContext } from "./testData";
const RETRY_COUNT = 3; const RETRY_COUNT = 3;
@@ -55,6 +56,9 @@ export const defaultAccounts: Record<TestAccount, string> = {
export const resourceGroupName = process.env.DE_TEST_RESOURCE_GROUP ?? "de-e2e-tests"; export const resourceGroupName = process.env.DE_TEST_RESOURCE_GROUP ?? "de-e2e-tests";
export const subscriptionId = process.env.DE_TEST_SUBSCRIPTION_ID ?? "69e02f2d-f059-4409-9eac-97e8a276ae2c"; export const subscriptionId = process.env.DE_TEST_SUBSCRIPTION_ID ?? "69e02f2d-f059-4409-9eac-97e8a276ae2c";
export const TEST_AUTOSCALE_THROUGHPUT_RU = 1000; export const TEST_AUTOSCALE_THROUGHPUT_RU = 1000;
export const TEST_AUTOSCALE_MAX_THROUGHPUT_RU_2K = 2000;
export const TEST_MANUAL_THROUGHPUT_RU_2K = 2000;
export const ONE_MINUTE_MS: number = 60 * 1000;
function tryGetStandardName(accountType: TestAccount) { function tryGetStandardName(accountType: TestAccount) {
if (process.env.DE_TEST_ACCOUNT_PREFIX) { if (process.env.DE_TEST_ACCOUNT_PREFIX) {
@@ -319,6 +323,11 @@ type PanelOpenOptions = {
closeTimeout?: number; closeTimeout?: number;
}; };
export enum CommandBarButton {
Save = "Save",
ExecuteQuery = "Execute Query",
}
/** Helper class that provides locator methods for DataExplorer components, on top of a Frame */ /** Helper class that provides locator methods for DataExplorer components, on top of a Frame */
export class DataExplorer { export class DataExplorer {
constructor(public frame: Frame) {} constructor(public frame: Frame) {}
@@ -348,8 +357,8 @@ export class DataExplorer {
} }
/** Select the command bar button with the specified label */ /** Select the command bar button with the specified label */
commandBarButton(label: string): Locator { commandBarButton(commandBarButton: CommandBarButton): Locator {
return this.frame.getByTestId(`CommandBar/Button:${label}`).and(this.frame.locator("css=button")); return this.frame.getByTestId(`CommandBar/Button:${commandBarButton}`).and(this.frame.locator("css=button"));
} }
dialogButton(label: string): Locator { dialogButton(label: string): Locator {
@@ -445,6 +454,22 @@ export class DataExplorer {
await panel.waitFor({ state: "detached", timeout: options.closeTimeout }); await panel.waitFor({ state: "detached", timeout: options.closeTimeout });
} }
/** Opens the Scale & Settings panel for the specified container */
async openScaleAndSettings(context: TestContainerContext): Promise<void> {
const containerNode = await this.waitForContainerNode(context.database.id, context.container.id);
await containerNode.expand();
const scaleAndSettingsButton = this.frame.getByTestId(
`TreeNode:${context.database.id}/${context.container.id}/Scale & Settings`,
);
await scaleAndSettingsButton.click();
}
/** Gets the console message element */
getConsoleMessage(): Locator {
return this.frame.getByTestId("notification-console/header-status");
}
/** Waits for the Data Explorer app to load */ /** Waits for the Data Explorer app to load */
static async waitForExplorer(page: Page) { static async waitForExplorer(page: Page) {
const iframeElement = await page.getByTestId("DataExplorerFrame").elementHandle(); const iframeElement = await page.getByTestId("DataExplorerFrame").elementHandle();

View File

@@ -1,6 +1,6 @@
import { expect, test } from "@playwright/test"; import { expect, test } from "@playwright/test";
import { DataExplorer, Editor, QueryTab, TestAccount } from "../fx"; import { CommandBarButton, DataExplorer, Editor, QueryTab, TestAccount } from "../fx";
import { TestContainerContext, TestItem, createTestSQLContainer } from "../testData"; import { TestContainerContext, TestItem, createTestSQLContainer } from "../testData";
let context: TestContainerContext = null!; let context: TestContainerContext = null!;
@@ -37,7 +37,7 @@ test.afterAll("Delete Test Database", async () => {
test("Query results", async () => { test("Query results", async () => {
// Run the query and verify the results // Run the query and verify the results
await queryEditor.locator.click(); await queryEditor.locator.click();
const executeQueryButton = explorer.commandBarButton("Execute Query"); const executeQueryButton = explorer.commandBarButton(CommandBarButton.ExecuteQuery);
await executeQueryButton.click(); await executeQueryButton.click();
await expect(queryTab.resultsEditor.locator).toBeAttached({ timeout: 60 * 1000 }); await expect(queryTab.resultsEditor.locator).toBeAttached({ timeout: 60 * 1000 });
@@ -59,7 +59,7 @@ test("Query results", async () => {
test("Query stats", async () => { test("Query stats", async () => {
// Run the query and verify the results // Run the query and verify the results
await queryEditor.locator.click(); await queryEditor.locator.click();
const executeQueryButton = explorer.commandBarButton("Execute Query"); const executeQueryButton = explorer.commandBarButton(CommandBarButton.ExecuteQuery);
await executeQueryButton.click(); await executeQueryButton.click();
await expect(queryTab.resultsEditor.locator).toBeAttached({ timeout: 60 * 1000 }); await expect(queryTab.resultsEditor.locator).toBeAttached({ timeout: 60 * 1000 });
@@ -77,7 +77,7 @@ test("Query errors", async () => {
await queryEditor.setText("SELECT\n glarb(c.id),\n blarg(c.id)\nFROM c"); await queryEditor.setText("SELECT\n glarb(c.id),\n blarg(c.id)\nFROM c");
// Run the query and verify the results // Run the query and verify the results
const executeQueryButton = explorer.commandBarButton("Execute Query"); const executeQueryButton = explorer.commandBarButton(CommandBarButton.ExecuteQuery);
await executeQueryButton.click(); await executeQueryButton.click();
await expect(queryTab.errorList).toBeAttached({ timeout: 60 * 1000 }); await expect(queryTab.errorList).toBeAttached({ timeout: 60 * 1000 });

View File

@@ -0,0 +1,129 @@
import { expect, Locator, test } from "@playwright/test";
import {
CommandBarButton,
DataExplorer,
ONE_MINUTE_MS,
TEST_AUTOSCALE_MAX_THROUGHPUT_RU_2K,
TEST_MANUAL_THROUGHPUT_RU_2K,
TestAccount,
} from "../../fx";
import { createTestSQLContainer, TestContainerContext } from "../../testData";
test.describe("Autoscale and Manual throughput", () => {
let context: TestContainerContext = null!;
let explorer: DataExplorer = null!;
test.beforeAll("Create Test Database", async () => {
context = await createTestSQLContainer(true);
});
test.beforeEach("Open container settings", async ({ page }) => {
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();
});
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();
// Update autoscale max throughput
await getThroughputInput("autopilot").fill(TEST_AUTOSCALE_MAX_THROUGHPUT_RU_2K.toString());
// Save
await explorer.commandBarButton(CommandBarButton.Save).click();
// Read console message
await expect(explorer.getConsoleMessage()).toContainText(
`Successfully updated offer for collection ${context.container.id}`,
{
timeout: 2 * ONE_MINUTE_MS,
},
);
});
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 expect(explorer.commandBarButton(CommandBarButton.Save)).toBeDisabled();
await expect(getThroughputInputErrorMessage("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 expect(explorer.commandBarButton(CommandBarButton.Save)).toBeDisabled();
await expect(getThroughputInputErrorMessage("autopilot")).toContainText(
"Throughput value must be in increments of 1000",
);
});
test("Update manual throughput", async () => {
await getThroughputInput("manual").fill(TEST_MANUAL_THROUGHPUT_RU_2K.toString());
await explorer.commandBarButton(CommandBarButton.Save).click();
await expect(explorer.getConsoleMessage()).toContainText(
`Successfully updated offer for collection ${context.container.id}`,
{
timeout: 2 * ONE_MINUTE_MS,
},
);
});
test("Update manual throughput passed allowed limit", async () => {
// 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 manual throughput above allowed limit
await getThroughputInput("manual").fill((softAllowedMaxThroughput * 10).toString());
await expect(explorer.commandBarButton(CommandBarButton.Save)).toBeDisabled();
await expect(getThroughputInputErrorMessage("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`);
};
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,
},
);
};
});

View File

@@ -0,0 +1,70 @@
import { expect, test } from "@playwright/test";
import { CommandBarButton, DataExplorer, ONE_MINUTE_MS, TestAccount } from "../../fx";
import { createTestSQLContainer, TestContainerContext } from "../../testData";
test.describe("Settings under Scale & Settings", () => {
let context: TestContainerContext = null!;
let explorer: DataExplorer = null!;
test.beforeAll("Create Test Database", async () => {
context = await createTestSQLContainer(true);
});
test.beforeEach("Open Settings tab under Scale & Settings", async ({ page }) => {
explorer = await DataExplorer.open(page, TestAccount.SQL);
const containerNode = await explorer.waitForContainerNode(context.database.id, context.container.id);
await containerNode.expand();
// Click Scale & Settings and open Scale tab
await explorer.openScaleAndSettings(context);
const settingsTab = explorer.frame.getByTestId("settings-tab-header/SubSettingsTab");
await settingsTab.click();
});
test.afterAll("Delete Test Database", async () => {
await context?.dispose();
});
test("Update TTL to On (no default)", async () => {
const ttlOnNoDefaultRadioButton = explorer.frame.getByRole("radio", { name: "ttl-on-no-default-option" });
await ttlOnNoDefaultRadioButton.click();
await explorer.commandBarButton(CommandBarButton.Save).click();
await expect(explorer.getConsoleMessage()).toContainText(`Successfully updated container ${context.container.id}`, {
timeout: ONE_MINUTE_MS,
});
});
test("Update TTL to On (with user entry)", async () => {
const ttlOnRadioButton = explorer.frame.getByRole("radio", { name: "ttl-on-option" });
await ttlOnRadioButton.click();
// Enter TTL seconds
const ttlInput = explorer.frame.getByTestId("ttl-input");
await ttlInput.fill("30000");
await explorer.commandBarButton(CommandBarButton.Save).click();
await expect(explorer.getConsoleMessage()).toContainText(`Successfully updated container ${context.container.id}`, {
timeout: ONE_MINUTE_MS,
});
});
test("Update TTL to Off", async () => {
// By default TTL is set to off so we need to first set it to On
const ttlOnNoDefaultRadioButton = explorer.frame.getByRole("radio", { name: "ttl-on-no-default-option" });
await ttlOnNoDefaultRadioButton.click();
await explorer.commandBarButton(CommandBarButton.Save).click();
await expect(explorer.getConsoleMessage()).toContainText(`Successfully updated container ${context.container.id}`, {
timeout: ONE_MINUTE_MS,
});
// Set it to Off
const ttlOffRadioButton = explorer.frame.getByRole("radio", { name: "ttl-off-option" });
await ttlOffRadioButton.click();
await explorer.commandBarButton(CommandBarButton.Save).click();
await expect(explorer.getConsoleMessage()).toContainText(`Successfully updated container ${context.container.id}`, {
timeout: ONE_MINUTE_MS,
});
});
});