mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-18 16:31:31 +00:00
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:
@@ -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 (
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
29
test/fx.ts
29
test/fx.ts
@@ -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();
|
||||||
|
|||||||
@@ -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 });
|
||||||
|
|||||||
129
test/sql/scaleAndSettings/scale.spec.ts
Normal file
129
test/sql/scaleAndSettings/scale.spec.ts
Normal 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,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
});
|
||||||
70
test/sql/scaleAndSettings/settings.spec.ts
Normal file
70
test/sql/scaleAndSettings/settings.spec.ts
Normal 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,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user