mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-25 11:51:07 +00:00
Add test infrastructure and data-test attributes for Container Copy e2e testing
This commit is contained in:
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
@@ -192,6 +192,9 @@ jobs:
|
||||
NOSQL_READONLY_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://github-e2etests-sql-readonly.documents.azure.com/.default" -o tsv --query accessToken)
|
||||
echo "::add-mask::$NOSQL_READONLY_TESTACCOUNT_TOKEN"
|
||||
echo NOSQL_READONLY_TESTACCOUNT_TOKEN=$NOSQL_READONLY_TESTACCOUNT_TOKEN >> $GITHUB_ENV
|
||||
NOSQL_CONTAINERCOPY_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://github-e2etests-sql-containercopyonly.documents.azure.com/.default" -o tsv --query accessToken)
|
||||
echo "::add-mask::$NOSQL_CONTAINERCOPY_TESTACCOUNT_TOKEN"
|
||||
echo NOSQL_CONTAINERCOPY_TESTACCOUNT_TOKEN=$NOSQL_CONTAINERCOPY_TESTACCOUNT_TOKEN >> $GITHUB_ENV
|
||||
TABLE_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://github-e2etests-tables.documents.azure.com/.default" -o tsv --query accessToken)
|
||||
echo "::add-mask::$TABLE_TESTACCOUNT_TOKEN"
|
||||
echo TABLE_TESTACCOUNT_TOKEN=$TABLE_TESTACCOUNT_TOKEN >> $GITHUB_ENV
|
||||
|
||||
@@ -13,6 +13,7 @@ const LoadingOverlay: React.FC<LoadingOverlayProps> = ({ isLoading, label }) =>
|
||||
|
||||
return (
|
||||
<Overlay
|
||||
data-test="loading-overlay"
|
||||
styles={{
|
||||
root: {
|
||||
backgroundColor: "rgba(255,255,255,0.9)",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
exports[`LoadingOverlay should handle long labels properly 1`] = `
|
||||
<div
|
||||
class="ms-Overlay root-109"
|
||||
data-test="loading-overlay"
|
||||
>
|
||||
<div
|
||||
class="ms-Spinner root-111"
|
||||
@@ -22,6 +23,7 @@ exports[`LoadingOverlay should handle long labels properly 1`] = `
|
||||
exports[`LoadingOverlay should render loading overlay when isLoading is true 1`] = `
|
||||
<div
|
||||
class="ms-Overlay root-109"
|
||||
data-test="loading-overlay"
|
||||
>
|
||||
<div
|
||||
class="ms-Spinner root-111"
|
||||
@@ -41,6 +43,7 @@ exports[`LoadingOverlay should render loading overlay when isLoading is true 1`]
|
||||
exports[`LoadingOverlay should render loading overlay with custom label 1`] = `
|
||||
<div
|
||||
class="ms-Overlay root-109"
|
||||
data-test="loading-overlay"
|
||||
>
|
||||
<div
|
||||
class="ms-Spinner root-111"
|
||||
@@ -60,6 +63,7 @@ exports[`LoadingOverlay should render loading overlay with custom label 1`] = `
|
||||
exports[`LoadingOverlay should render loading overlay with empty label 1`] = `
|
||||
<div
|
||||
class="ms-Overlay root-109"
|
||||
data-test="loading-overlay"
|
||||
>
|
||||
<div
|
||||
class="ms-Spinner root-111"
|
||||
|
||||
@@ -35,6 +35,7 @@ const AddManagedIdentity: React.FC<AddManagedIdentityProps> = () => {
|
||||
<InfoTooltip content={managedIdentityTooltip} />
|
||||
</Text>
|
||||
<Toggle
|
||||
data-test="btn-toggle"
|
||||
checked={systemAssigned}
|
||||
onText={ContainerCopyMessages.toggleBtn.onText}
|
||||
offText={ContainerCopyMessages.toggleBtn.offText}
|
||||
|
||||
@@ -65,6 +65,7 @@ const AddReadPermissionToDefaultIdentity: React.FC<AddReadPermissionToDefaultIde
|
||||
<InfoTooltip content={TooltipContent} />
|
||||
</Text>
|
||||
<Toggle
|
||||
data-test="btn-toggle"
|
||||
checked={readPermissionAssigned}
|
||||
onText={ContainerCopyMessages.toggleBtn.onText}
|
||||
offText={ContainerCopyMessages.toggleBtn.offText}
|
||||
|
||||
@@ -12,7 +12,7 @@ import { useCopyJobPrerequisitesCache } from "../../Utils/useCopyJobPrerequisite
|
||||
import usePermissionSections, { PermissionGroupConfig, PermissionSectionConfig } from "./hooks/usePermissionsSection";
|
||||
|
||||
const PermissionSection: React.FC<PermissionSectionConfig> = ({ id, title, Component, completed, disabled }) => (
|
||||
<AccordionItem key={id} value={id} disabled={disabled}>
|
||||
<AccordionItem key={id} value={id} disabled={disabled} data-test="accordion-item">
|
||||
<AccordionHeader className="accordionHeader">
|
||||
<Text className="accordionHeaderText" variant="medium">
|
||||
{title}
|
||||
@@ -25,13 +25,13 @@ const PermissionSection: React.FC<PermissionSectionConfig> = ({ id, title, Compo
|
||||
height={completed ? 20 : 24}
|
||||
/>
|
||||
</AccordionHeader>
|
||||
<AccordionPanel aria-disabled={disabled} className="accordionPanel">
|
||||
<AccordionPanel aria-disabled={disabled} className="accordionPanel" data-test="accordion-panel">
|
||||
<Component />
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
);
|
||||
|
||||
const PermissionGroup: React.FC<PermissionGroupConfig> = ({ title, description, sections }) => {
|
||||
const PermissionGroup: React.FC<PermissionGroupConfig> = ({ id, title, description, sections }) => {
|
||||
const [openItems, setOpenItems] = React.useState<string[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -44,6 +44,7 @@ const PermissionGroup: React.FC<PermissionGroupConfig> = ({ title, description,
|
||||
|
||||
return (
|
||||
<Stack
|
||||
data-test={`permission-group-container-${id}`}
|
||||
tokens={{ childrenGap: 15 }}
|
||||
styles={{
|
||||
root: {
|
||||
@@ -99,7 +100,11 @@ const AssignPermissions = () => {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Stack className="assignPermissionsContainer" tokens={{ childrenGap: 20 }}>
|
||||
<Stack
|
||||
data-test="Panel:AssignPermissionsContainer"
|
||||
className="assignPermissionsContainer"
|
||||
tokens={{ childrenGap: 20 }}
|
||||
>
|
||||
<Text variant="medium">
|
||||
{isSameAccount && copyJobState.migrationType === CopyJobMigrationType.Online
|
||||
? ContainerCopyMessages.assignPermissions.intraAccountOnlineDescription(
|
||||
|
||||
@@ -31,6 +31,7 @@ const DefaultManagedIdentity: React.FC<AddManagedIdentityProps> = () => {
|
||||
<InfoTooltip content={managedIdentityTooltip} />
|
||||
</div>
|
||||
<Toggle
|
||||
data-test="btn-toggle"
|
||||
checked={defaultSystemAssigned}
|
||||
onText={ContainerCopyMessages.toggleBtn.onText}
|
||||
offText={ContainerCopyMessages.toggleBtn.offText}
|
||||
|
||||
@@ -127,6 +127,7 @@ const PointInTimeRestore: React.FC = () => {
|
||||
<Stack.Item>
|
||||
{showRefreshButton ? (
|
||||
<PrimaryButton
|
||||
data-test="pointInTimeRestore:RefreshBtn"
|
||||
className="fullWidth"
|
||||
text={ContainerCopyMessages.refreshButtonLabel}
|
||||
iconProps={{ iconName: "Refresh" }}
|
||||
@@ -134,6 +135,7 @@ const PointInTimeRestore: React.FC = () => {
|
||||
/>
|
||||
) : (
|
||||
<PrimaryButton
|
||||
data-test="pointInTimeRestore:PrimaryBtn"
|
||||
className="fullWidth"
|
||||
text={loading ? "" : ContainerCopyMessages.pointInTimeRestore.buttonText}
|
||||
{...(loading ? { iconProps: { iconName: "SyncStatusSolid" } } : {})}
|
||||
|
||||
@@ -67,6 +67,7 @@ exports[`AddManagedIdentity Snapshot Tests renders initial state correctly 1`] =
|
||||
class="ms-Toggle-background pill-117"
|
||||
data-is-focusable="true"
|
||||
data-ktp-target="true"
|
||||
data-test="btn-toggle"
|
||||
id="Toggle1"
|
||||
role="switch"
|
||||
type="button"
|
||||
@@ -154,6 +155,7 @@ exports[`AddManagedIdentity Snapshot Tests renders loading state 1`] = `
|
||||
class="ms-Toggle-background pill-121"
|
||||
data-is-focusable="true"
|
||||
data-ktp-target="true"
|
||||
data-test="btn-toggle"
|
||||
id="Toggle11"
|
||||
role="switch"
|
||||
type="button"
|
||||
@@ -173,10 +175,12 @@ exports[`AddManagedIdentity Snapshot Tests renders loading state 1`] = `
|
||||
</div>
|
||||
<div
|
||||
class="ms-Stack popover-container foreground loading css-123"
|
||||
data-test="popover-container"
|
||||
style="max-width: 450px;"
|
||||
>
|
||||
<div
|
||||
class="ms-Overlay root-135"
|
||||
data-test="loading-overlay"
|
||||
>
|
||||
<div
|
||||
class="ms-Spinner root-137"
|
||||
@@ -323,6 +327,7 @@ exports[`AddManagedIdentity Snapshot Tests renders with toggle on and popover vi
|
||||
class="ms-Toggle-background pill-121"
|
||||
data-is-focusable="true"
|
||||
data-ktp-target="true"
|
||||
data-test="btn-toggle"
|
||||
id="Toggle3"
|
||||
role="switch"
|
||||
type="button"
|
||||
@@ -342,6 +347,7 @@ exports[`AddManagedIdentity Snapshot Tests renders with toggle on and popover vi
|
||||
</div>
|
||||
<div
|
||||
class="ms-Stack popover-container foreground css-123"
|
||||
data-test="popover-container"
|
||||
style="max-width: 450px;"
|
||||
>
|
||||
<span
|
||||
|
||||
@@ -41,6 +41,7 @@ exports[`AddReadPermissionToDefaultIdentity Component Edge Cases should handle m
|
||||
class="ms-Toggle-background pill-115"
|
||||
data-is-focusable="true"
|
||||
data-ktp-target="true"
|
||||
data-test="btn-toggle"
|
||||
id="Toggle17"
|
||||
role="switch"
|
||||
type="button"
|
||||
@@ -103,6 +104,7 @@ exports[`AddReadPermissionToDefaultIdentity Component Edge Cases should handle m
|
||||
class="ms-Toggle-background pill-115"
|
||||
data-is-focusable="true"
|
||||
data-ktp-target="true"
|
||||
data-test="btn-toggle"
|
||||
id="Toggle16"
|
||||
role="switch"
|
||||
type="button"
|
||||
@@ -165,6 +167,7 @@ exports[`AddReadPermissionToDefaultIdentity Component Rendering should render co
|
||||
class="ms-Toggle-background pill-115"
|
||||
data-is-focusable="true"
|
||||
data-ktp-target="true"
|
||||
data-test="btn-toggle"
|
||||
id="Toggle3"
|
||||
role="switch"
|
||||
type="button"
|
||||
@@ -227,6 +230,7 @@ exports[`AddReadPermissionToDefaultIdentity Component Rendering should render co
|
||||
class="ms-Toggle-background pill-119"
|
||||
data-is-focusable="true"
|
||||
data-ktp-target="true"
|
||||
data-test="btn-toggle"
|
||||
id="Toggle1"
|
||||
role="switch"
|
||||
type="button"
|
||||
@@ -314,6 +318,7 @@ exports[`AddReadPermissionToDefaultIdentity Component Rendering should render co
|
||||
class="ms-Toggle-background pill-115"
|
||||
data-is-focusable="true"
|
||||
data-ktp-target="true"
|
||||
data-test="btn-toggle"
|
||||
id="Toggle0"
|
||||
role="switch"
|
||||
type="button"
|
||||
@@ -376,6 +381,7 @@ exports[`AddReadPermissionToDefaultIdentity Component Rendering should render co
|
||||
class="ms-Toggle-background pill-115"
|
||||
data-is-focusable="true"
|
||||
data-ktp-target="true"
|
||||
data-test="btn-toggle"
|
||||
id="Toggle2"
|
||||
role="switch"
|
||||
type="button"
|
||||
|
||||
@@ -4,6 +4,7 @@ exports[`AssignPermissions Component Accordion Behavior should render accordion
|
||||
<div>
|
||||
<div
|
||||
class="ms-Stack assignPermissionsContainer css-109"
|
||||
data-test="Panel:AssignPermissionsContainer"
|
||||
>
|
||||
<span
|
||||
class="css-110"
|
||||
@@ -15,6 +16,7 @@ exports[`AssignPermissions Component Accordion Behavior should render accordion
|
||||
>
|
||||
<div
|
||||
class="ms-Stack css-112"
|
||||
data-test="permission-group-container-testGroup"
|
||||
>
|
||||
<div
|
||||
class="ms-Stack css-113"
|
||||
@@ -36,6 +38,7 @@ exports[`AssignPermissions Component Accordion Behavior should render accordion
|
||||
>
|
||||
<div
|
||||
class="fui-AccordionItem"
|
||||
data-test="accordion-item"
|
||||
>
|
||||
<div
|
||||
class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5"
|
||||
@@ -85,6 +88,7 @@ exports[`AssignPermissions Component Accordion Behavior should render accordion
|
||||
</div>
|
||||
<div
|
||||
class="fui-AccordionItem"
|
||||
data-test="accordion-item"
|
||||
>
|
||||
<div
|
||||
class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5"
|
||||
@@ -134,6 +138,7 @@ exports[`AssignPermissions Component Accordion Behavior should render accordion
|
||||
<div
|
||||
aria-disabled="false"
|
||||
class="fui-AccordionPanel accordionPanel ___1rufncu_1hx1scr f1axvtxu"
|
||||
data-test="accordion-panel"
|
||||
>
|
||||
<div>
|
||||
Incomplete Component
|
||||
@@ -142,6 +147,7 @@ exports[`AssignPermissions Component Accordion Behavior should render accordion
|
||||
</div>
|
||||
<div
|
||||
class="fui-AccordionItem"
|
||||
data-test="accordion-item"
|
||||
>
|
||||
<div
|
||||
class="fui-AccordionHeader accordionHeader ___lyghz50_53x5ri0 f1s2aq7o f1c21dwh f1s184ao ft85np5 fwrgwhw"
|
||||
@@ -201,6 +207,7 @@ exports[`AssignPermissions Component Edge Cases should calculate correct indent
|
||||
<div>
|
||||
<div
|
||||
class="ms-Stack assignPermissionsContainer css-109"
|
||||
data-test="Panel:AssignPermissionsContainer"
|
||||
>
|
||||
<span
|
||||
class="css-110"
|
||||
@@ -212,6 +219,7 @@ exports[`AssignPermissions Component Edge Cases should calculate correct indent
|
||||
>
|
||||
<div
|
||||
class="ms-Stack css-112"
|
||||
data-test="permission-group-container-testGroup"
|
||||
>
|
||||
<div
|
||||
class="ms-Stack css-113"
|
||||
@@ -233,6 +241,7 @@ exports[`AssignPermissions Component Edge Cases should calculate correct indent
|
||||
>
|
||||
<div
|
||||
class="fui-AccordionItem"
|
||||
data-test="accordion-item"
|
||||
>
|
||||
<div
|
||||
class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5"
|
||||
@@ -282,6 +291,7 @@ exports[`AssignPermissions Component Edge Cases should calculate correct indent
|
||||
</div>
|
||||
<div
|
||||
class="fui-AccordionItem"
|
||||
data-test="accordion-item"
|
||||
>
|
||||
<div
|
||||
class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5"
|
||||
@@ -331,6 +341,7 @@ exports[`AssignPermissions Component Edge Cases should calculate correct indent
|
||||
<div
|
||||
aria-disabled="false"
|
||||
class="fui-AccordionPanel accordionPanel ___1rufncu_1hx1scr f1axvtxu"
|
||||
data-test="accordion-panel"
|
||||
>
|
||||
<div>
|
||||
Incomplete Component
|
||||
@@ -339,6 +350,7 @@ exports[`AssignPermissions Component Edge Cases should calculate correct indent
|
||||
</div>
|
||||
<div
|
||||
class="fui-AccordionItem"
|
||||
data-test="accordion-item"
|
||||
>
|
||||
<div
|
||||
class="fui-AccordionHeader accordionHeader ___lyghz50_53x5ri0 f1s2aq7o f1c21dwh f1s184ao ft85np5 fwrgwhw"
|
||||
@@ -398,6 +410,7 @@ exports[`AssignPermissions Component Edge Cases should calculate correct indent
|
||||
<div>
|
||||
<div
|
||||
class="ms-Stack assignPermissionsContainer css-109"
|
||||
data-test="Panel:AssignPermissionsContainer"
|
||||
>
|
||||
<span
|
||||
class="css-110"
|
||||
@@ -409,6 +422,7 @@ exports[`AssignPermissions Component Edge Cases should calculate correct indent
|
||||
>
|
||||
<div
|
||||
class="ms-Stack css-112"
|
||||
data-test="permission-group-container-testGroup"
|
||||
>
|
||||
<div
|
||||
class="ms-Stack css-113"
|
||||
@@ -430,6 +444,7 @@ exports[`AssignPermissions Component Edge Cases should calculate correct indent
|
||||
>
|
||||
<div
|
||||
class="fui-AccordionItem"
|
||||
data-test="accordion-item"
|
||||
>
|
||||
<div
|
||||
class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5"
|
||||
@@ -479,6 +494,7 @@ exports[`AssignPermissions Component Edge Cases should calculate correct indent
|
||||
</div>
|
||||
<div
|
||||
class="fui-AccordionItem"
|
||||
data-test="accordion-item"
|
||||
>
|
||||
<div
|
||||
class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5"
|
||||
@@ -528,6 +544,7 @@ exports[`AssignPermissions Component Edge Cases should calculate correct indent
|
||||
<div
|
||||
aria-disabled="false"
|
||||
class="fui-AccordionPanel accordionPanel ___1rufncu_1hx1scr f1axvtxu"
|
||||
data-test="accordion-panel"
|
||||
>
|
||||
<div>
|
||||
Incomplete Component
|
||||
@@ -536,6 +553,7 @@ exports[`AssignPermissions Component Edge Cases should calculate correct indent
|
||||
</div>
|
||||
<div
|
||||
class="fui-AccordionItem"
|
||||
data-test="accordion-item"
|
||||
>
|
||||
<div
|
||||
class="fui-AccordionHeader accordionHeader ___lyghz50_53x5ri0 f1s2aq7o f1c21dwh f1s184ao ft85np5 fwrgwhw"
|
||||
@@ -595,6 +613,7 @@ exports[`AssignPermissions Component Edge Cases should handle missing account na
|
||||
<div>
|
||||
<div
|
||||
class="ms-Stack assignPermissionsContainer css-109"
|
||||
data-test="Panel:AssignPermissionsContainer"
|
||||
>
|
||||
<span
|
||||
class="css-110"
|
||||
@@ -606,6 +625,7 @@ exports[`AssignPermissions Component Edge Cases should handle missing account na
|
||||
>
|
||||
<div
|
||||
class="ms-Stack css-112"
|
||||
data-test="permission-group-container-testGroup"
|
||||
>
|
||||
<div
|
||||
class="ms-Stack css-113"
|
||||
@@ -627,6 +647,7 @@ exports[`AssignPermissions Component Edge Cases should handle missing account na
|
||||
>
|
||||
<div
|
||||
class="fui-AccordionItem"
|
||||
data-test="accordion-item"
|
||||
>
|
||||
<div
|
||||
class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5"
|
||||
@@ -676,6 +697,7 @@ exports[`AssignPermissions Component Edge Cases should handle missing account na
|
||||
</div>
|
||||
<div
|
||||
class="fui-AccordionItem"
|
||||
data-test="accordion-item"
|
||||
>
|
||||
<div
|
||||
class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5"
|
||||
@@ -725,6 +747,7 @@ exports[`AssignPermissions Component Edge Cases should handle missing account na
|
||||
<div
|
||||
aria-disabled="false"
|
||||
class="fui-AccordionPanel accordionPanel ___1rufncu_1hx1scr f1axvtxu"
|
||||
data-test="accordion-panel"
|
||||
>
|
||||
<div>
|
||||
Incomplete Component
|
||||
@@ -733,6 +756,7 @@ exports[`AssignPermissions Component Edge Cases should handle missing account na
|
||||
</div>
|
||||
<div
|
||||
class="fui-AccordionItem"
|
||||
data-test="accordion-item"
|
||||
>
|
||||
<div
|
||||
class="fui-AccordionHeader accordionHeader ___lyghz50_53x5ri0 f1s2aq7o f1c21dwh f1s184ao ft85np5 fwrgwhw"
|
||||
@@ -792,6 +816,7 @@ exports[`AssignPermissions Component Permission Groups should render multiple pe
|
||||
<div>
|
||||
<div
|
||||
class="ms-Stack assignPermissionsContainer css-109"
|
||||
data-test="Panel:AssignPermissionsContainer"
|
||||
>
|
||||
<span
|
||||
class="css-110"
|
||||
@@ -803,6 +828,7 @@ exports[`AssignPermissions Component Permission Groups should render multiple pe
|
||||
>
|
||||
<div
|
||||
class="ms-Stack css-112"
|
||||
data-test="permission-group-container-crossAccountConfigs"
|
||||
>
|
||||
<div
|
||||
class="ms-Stack css-113"
|
||||
@@ -824,6 +850,7 @@ exports[`AssignPermissions Component Permission Groups should render multiple pe
|
||||
>
|
||||
<div
|
||||
class="fui-AccordionItem"
|
||||
data-test="accordion-item"
|
||||
>
|
||||
<div
|
||||
class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5"
|
||||
@@ -875,6 +902,7 @@ exports[`AssignPermissions Component Permission Groups should render multiple pe
|
||||
</div>
|
||||
<div
|
||||
class="ms-Stack css-112"
|
||||
data-test="permission-group-container-onlineConfigs"
|
||||
>
|
||||
<div
|
||||
class="ms-Stack css-113"
|
||||
@@ -896,6 +924,7 @@ exports[`AssignPermissions Component Permission Groups should render multiple pe
|
||||
>
|
||||
<div
|
||||
class="fui-AccordionItem"
|
||||
data-test="accordion-item"
|
||||
>
|
||||
<div
|
||||
class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5"
|
||||
@@ -945,6 +974,7 @@ exports[`AssignPermissions Component Permission Groups should render multiple pe
|
||||
<div
|
||||
aria-disabled="false"
|
||||
class="fui-AccordionPanel accordionPanel ___1rufncu_1hx1scr f1axvtxu"
|
||||
data-test="accordion-panel"
|
||||
>
|
||||
<div
|
||||
data-testid="online-copy-enabled"
|
||||
@@ -964,6 +994,7 @@ exports[`AssignPermissions Component Permission Groups should render online migr
|
||||
<div>
|
||||
<div
|
||||
class="ms-Stack assignPermissionsContainer css-109"
|
||||
data-test="Panel:AssignPermissionsContainer"
|
||||
>
|
||||
<span
|
||||
class="css-110"
|
||||
@@ -975,6 +1006,7 @@ exports[`AssignPermissions Component Permission Groups should render online migr
|
||||
>
|
||||
<div
|
||||
class="ms-Stack css-112"
|
||||
data-test="permission-group-container-onlineConfigs"
|
||||
>
|
||||
<div
|
||||
class="ms-Stack css-113"
|
||||
@@ -996,6 +1028,7 @@ exports[`AssignPermissions Component Permission Groups should render online migr
|
||||
>
|
||||
<div
|
||||
class="fui-AccordionItem"
|
||||
data-test="accordion-item"
|
||||
>
|
||||
<div
|
||||
class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5"
|
||||
@@ -1045,6 +1078,7 @@ exports[`AssignPermissions Component Permission Groups should render online migr
|
||||
</div>
|
||||
<div
|
||||
class="fui-AccordionItem"
|
||||
data-test="accordion-item"
|
||||
>
|
||||
<div
|
||||
class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5"
|
||||
@@ -1094,6 +1128,7 @@ exports[`AssignPermissions Component Permission Groups should render online migr
|
||||
<div
|
||||
aria-disabled="false"
|
||||
class="fui-AccordionPanel accordionPanel ___1rufncu_1hx1scr f1axvtxu"
|
||||
data-test="accordion-panel"
|
||||
>
|
||||
<div
|
||||
data-testid="online-copy-enabled"
|
||||
@@ -1113,6 +1148,7 @@ exports[`AssignPermissions Component Permission Groups should render permission
|
||||
<div>
|
||||
<div
|
||||
class="ms-Stack assignPermissionsContainer css-109"
|
||||
data-test="Panel:AssignPermissionsContainer"
|
||||
>
|
||||
<span
|
||||
class="css-110"
|
||||
@@ -1124,6 +1160,7 @@ exports[`AssignPermissions Component Permission Groups should render permission
|
||||
>
|
||||
<div
|
||||
class="ms-Stack css-112"
|
||||
data-test="permission-group-container-crossAccountConfigs"
|
||||
>
|
||||
<div
|
||||
class="ms-Stack css-113"
|
||||
@@ -1145,6 +1182,7 @@ exports[`AssignPermissions Component Permission Groups should render permission
|
||||
>
|
||||
<div
|
||||
class="fui-AccordionItem"
|
||||
data-test="accordion-item"
|
||||
>
|
||||
<div
|
||||
class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5"
|
||||
@@ -1194,6 +1232,7 @@ exports[`AssignPermissions Component Permission Groups should render permission
|
||||
</div>
|
||||
<div
|
||||
class="fui-AccordionItem"
|
||||
data-test="accordion-item"
|
||||
>
|
||||
<div
|
||||
class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5"
|
||||
@@ -1243,6 +1282,7 @@ exports[`AssignPermissions Component Permission Groups should render permission
|
||||
<div
|
||||
aria-disabled="false"
|
||||
class="fui-AccordionPanel accordionPanel ___1rufncu_1hx1scr f1axvtxu"
|
||||
data-test="accordion-panel"
|
||||
>
|
||||
<div
|
||||
data-testid="add-read-permission"
|
||||
@@ -1262,6 +1302,7 @@ exports[`AssignPermissions Component Rendering should render without crashing wi
|
||||
<div>
|
||||
<div
|
||||
class="ms-Stack assignPermissionsContainer css-109"
|
||||
data-test="Panel:AssignPermissionsContainer"
|
||||
>
|
||||
<span
|
||||
class="css-110"
|
||||
@@ -1283,6 +1324,7 @@ exports[`AssignPermissions Component Rendering should render without crashing wi
|
||||
<div>
|
||||
<div
|
||||
class="ms-Stack assignPermissionsContainer css-109"
|
||||
data-test="Panel:AssignPermissionsContainer"
|
||||
>
|
||||
<span
|
||||
class="css-110"
|
||||
|
||||
@@ -41,6 +41,7 @@ exports[`DefaultManagedIdentity Edge Cases should handle missing account name gr
|
||||
class="ms-Toggle-background pill-115"
|
||||
data-is-focusable="true"
|
||||
data-ktp-target="true"
|
||||
data-test="btn-toggle"
|
||||
id="Toggle14"
|
||||
role="switch"
|
||||
type="button"
|
||||
@@ -103,6 +104,7 @@ exports[`DefaultManagedIdentity Edge Cases should handle null account 1`] = `
|
||||
class="ms-Toggle-background pill-115"
|
||||
data-is-focusable="true"
|
||||
data-ktp-target="true"
|
||||
data-test="btn-toggle"
|
||||
id="Toggle15"
|
||||
role="switch"
|
||||
type="button"
|
||||
@@ -165,6 +167,7 @@ exports[`DefaultManagedIdentity Loading States should render loading state snaps
|
||||
class="ms-Toggle-background pill-119"
|
||||
data-is-focusable="true"
|
||||
data-ktp-target="true"
|
||||
data-test="btn-toggle"
|
||||
id="Toggle10"
|
||||
role="switch"
|
||||
type="button"
|
||||
@@ -256,6 +259,7 @@ exports[`DefaultManagedIdentity Rendering should render correctly with default s
|
||||
class="ms-Toggle-background pill-115"
|
||||
data-is-focusable="true"
|
||||
data-ktp-target="true"
|
||||
data-test="btn-toggle"
|
||||
id="Toggle0"
|
||||
role="switch"
|
||||
type="button"
|
||||
@@ -318,6 +322,7 @@ exports[`DefaultManagedIdentity Toggle Interactions should render toggle with ch
|
||||
class="ms-Toggle-background pill-119"
|
||||
data-is-focusable="true"
|
||||
data-ktp-target="true"
|
||||
data-test="btn-toggle"
|
||||
id="Toggle7"
|
||||
role="switch"
|
||||
type="button"
|
||||
|
||||
@@ -56,6 +56,7 @@ exports[`PointInTimeRestore Initial Render should render correctly with default
|
||||
<button
|
||||
class="ms-Button ms-Button--primary fullWidth root-115"
|
||||
data-is-focusable="true"
|
||||
data-test="pointInTimeRestore:PrimaryBtn"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
@@ -131,6 +132,7 @@ exports[`PointInTimeRestore Initial Render should render with empty account name
|
||||
<button
|
||||
class="ms-Button ms-Button--primary fullWidth root-115"
|
||||
data-is-focusable="true"
|
||||
data-test="pointInTimeRestore:PrimaryBtn"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
@@ -161,6 +163,7 @@ exports[`PointInTimeRestore Snapshots should match snapshot in loading state 1`]
|
||||
>
|
||||
<div
|
||||
class="ms-Overlay root-123"
|
||||
data-test="loading-overlay"
|
||||
>
|
||||
<div
|
||||
class="ms-Spinner root-125"
|
||||
@@ -223,6 +226,7 @@ exports[`PointInTimeRestore Snapshots should match snapshot in loading state 1`]
|
||||
aria-disabled="true"
|
||||
class="ms-Button ms-Button--primary is-disabled fullWidth root-128"
|
||||
data-is-focusable="false"
|
||||
data-test="pointInTimeRestore:PrimaryBtn"
|
||||
disabled=""
|
||||
type="button"
|
||||
>
|
||||
@@ -301,6 +305,7 @@ exports[`PointInTimeRestore Snapshots should match snapshot with refresh button
|
||||
<button
|
||||
class="ms-Button ms-Button--primary fullWidth root-115"
|
||||
data-is-focusable="true"
|
||||
data-test="pointInTimeRestore:RefreshBtn"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
|
||||
@@ -17,6 +17,7 @@ const PopoverContainer: React.FC<PopoverContainerProps> = React.memo(
|
||||
({ isLoading = false, title, children, onPrimary, onCancel }) => {
|
||||
return (
|
||||
<Stack
|
||||
data-test="popover-container"
|
||||
className={`popover-container foreground ${isLoading ? "loading" : ""}`}
|
||||
tokens={{ childrenGap: 20 }}
|
||||
style={{ maxWidth: 450 }}
|
||||
|
||||
@@ -4,6 +4,7 @@ exports[`PopoverMessage Component Edge Cases should handle empty string title 1`
|
||||
<div>
|
||||
<div
|
||||
class="ms-Stack popover-container foreground css-109"
|
||||
data-test="popover-container"
|
||||
style="max-width: 450px;"
|
||||
>
|
||||
<span
|
||||
@@ -71,6 +72,7 @@ exports[`PopoverMessage Component Edge Cases should handle null children 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="ms-Stack popover-container foreground css-109"
|
||||
data-test="popover-container"
|
||||
style="max-width: 450px;"
|
||||
>
|
||||
<span
|
||||
@@ -133,6 +135,7 @@ exports[`PopoverMessage Component Edge Cases should handle undefined children 1`
|
||||
<div>
|
||||
<div
|
||||
class="ms-Stack popover-container foreground css-109"
|
||||
data-test="popover-container"
|
||||
style="max-width: 450px;"
|
||||
>
|
||||
<span
|
||||
@@ -195,6 +198,7 @@ exports[`PopoverMessage Component Edge Cases should handle very long title 1`] =
|
||||
<div>
|
||||
<div
|
||||
class="ms-Stack popover-container foreground css-109"
|
||||
data-test="popover-container"
|
||||
style="max-width: 450px;"
|
||||
>
|
||||
<span
|
||||
@@ -266,6 +270,7 @@ exports[`PopoverMessage Component Rendering should render correctly when visible
|
||||
<div>
|
||||
<div
|
||||
class="ms-Stack popover-container foreground css-109"
|
||||
data-test="popover-container"
|
||||
style="max-width: 450px;"
|
||||
>
|
||||
<span
|
||||
@@ -335,6 +340,7 @@ exports[`PopoverMessage Component Rendering should render correctly with differe
|
||||
<div>
|
||||
<div
|
||||
class="ms-Stack popover-container foreground css-109"
|
||||
data-test="popover-container"
|
||||
style="max-width: 450px;"
|
||||
>
|
||||
<span
|
||||
@@ -409,6 +415,7 @@ exports[`PopoverMessage Component Rendering should render correctly with differe
|
||||
<div>
|
||||
<div
|
||||
class="ms-Stack popover-container foreground css-109"
|
||||
data-test="popover-container"
|
||||
style="max-width: 450px;"
|
||||
>
|
||||
<span
|
||||
@@ -478,6 +485,7 @@ exports[`PopoverMessage Component Rendering should render correctly with loading
|
||||
<div>
|
||||
<div
|
||||
class="ms-Stack popover-container foreground loading css-109"
|
||||
data-test="popover-container"
|
||||
style="max-width: 450px;"
|
||||
>
|
||||
<div
|
||||
|
||||
@@ -22,6 +22,7 @@ const CreateCopyJobScreens: React.FC = () => {
|
||||
<Stack.Item className="createCopyJobScreensContent">
|
||||
{contextError && (
|
||||
<MessageBar
|
||||
data-test="Panel:ErrorContainer"
|
||||
className="createCopyJobErrorMessageBar"
|
||||
messageBarType={MessageBarType.blocked}
|
||||
isMultiline={false}
|
||||
|
||||
@@ -31,17 +31,17 @@ const PreviewCopyJob: React.FC = () => {
|
||||
}));
|
||||
};
|
||||
return (
|
||||
<Stack tokens={{ childrenGap: 20 }} className="previewCopyJobContainer">
|
||||
<Stack tokens={{ childrenGap: 20 }} className="previewCopyJobContainer" data-test="Panel:PreviewCopyJob">
|
||||
<FieldRow label={ContainerCopyMessages.jobNameLabel}>
|
||||
<TextField value={jobName} onChange={onJobNameChange} />
|
||||
<TextField data-test="job-name-textfield" value={jobName} onChange={onJobNameChange} />
|
||||
</FieldRow>
|
||||
<Stack>
|
||||
<Text className="bold">{ContainerCopyMessages.sourceSubscriptionLabel}</Text>
|
||||
<Text>{copyJobState.source?.subscription?.displayName}</Text>
|
||||
<Text data-test="source-subscription-name">{copyJobState.source?.subscription?.displayName}</Text>
|
||||
</Stack>
|
||||
<Stack>
|
||||
<Text className="bold">{ContainerCopyMessages.sourceAccountLabel}</Text>
|
||||
<Text>{copyJobState.source?.account?.name}</Text>
|
||||
<Text data-test="source-account-name">{copyJobState.source?.account?.name}</Text>
|
||||
</Stack>
|
||||
<Stack>
|
||||
<DetailsList
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
exports[`PreviewCopyJob should handle special characters in database and container names 1`] = `
|
||||
<div
|
||||
class="ms-Stack previewCopyJobContainer css-109"
|
||||
data-test="Panel:PreviewCopyJob"
|
||||
>
|
||||
<div
|
||||
class="ms-Stack flex-row css-110"
|
||||
@@ -32,6 +33,7 @@ exports[`PreviewCopyJob should handle special characters in database and contain
|
||||
<input
|
||||
aria-invalid="false"
|
||||
class="ms-TextField-field field-115"
|
||||
data-test="job-name-textfield"
|
||||
id="TextField84"
|
||||
type="text"
|
||||
value="job-with@special#chars_123"
|
||||
@@ -51,6 +53,7 @@ exports[`PreviewCopyJob should handle special characters in database and contain
|
||||
</span>
|
||||
<span
|
||||
class="css-125"
|
||||
data-test="source-subscription-name"
|
||||
>
|
||||
Test Subscription
|
||||
</span>
|
||||
@@ -65,6 +68,7 @@ exports[`PreviewCopyJob should handle special characters in database and contain
|
||||
</span>
|
||||
<span
|
||||
class="css-125"
|
||||
data-test="source-account-name"
|
||||
>
|
||||
test-account
|
||||
</span>
|
||||
@@ -321,6 +325,7 @@ exports[`PreviewCopyJob should handle special characters in database and contain
|
||||
exports[`PreviewCopyJob should render component with cross-subscription setup 1`] = `
|
||||
<div
|
||||
class="ms-Stack previewCopyJobContainer css-109"
|
||||
data-test="Panel:PreviewCopyJob"
|
||||
>
|
||||
<div
|
||||
class="ms-Stack flex-row css-110"
|
||||
@@ -350,6 +355,7 @@ exports[`PreviewCopyJob should render component with cross-subscription setup 1`
|
||||
<input
|
||||
aria-invalid="false"
|
||||
class="ms-TextField-field field-115"
|
||||
data-test="job-name-textfield"
|
||||
id="TextField96"
|
||||
type="text"
|
||||
value=""
|
||||
@@ -369,6 +375,7 @@ exports[`PreviewCopyJob should render component with cross-subscription setup 1`
|
||||
</span>
|
||||
<span
|
||||
class="css-125"
|
||||
data-test="source-subscription-name"
|
||||
>
|
||||
Test Subscription
|
||||
</span>
|
||||
@@ -383,6 +390,7 @@ exports[`PreviewCopyJob should render component with cross-subscription setup 1`
|
||||
</span>
|
||||
<span
|
||||
class="css-125"
|
||||
data-test="source-account-name"
|
||||
>
|
||||
test-account
|
||||
</span>
|
||||
@@ -639,6 +647,7 @@ exports[`PreviewCopyJob should render component with cross-subscription setup 1`
|
||||
exports[`PreviewCopyJob should render with default state and empty job name 1`] = `
|
||||
<div
|
||||
class="ms-Stack previewCopyJobContainer css-109"
|
||||
data-test="Panel:PreviewCopyJob"
|
||||
>
|
||||
<div
|
||||
class="ms-Stack flex-row css-110"
|
||||
@@ -668,6 +677,7 @@ exports[`PreviewCopyJob should render with default state and empty job name 1`]
|
||||
<input
|
||||
aria-invalid="false"
|
||||
class="ms-TextField-field field-115"
|
||||
data-test="job-name-textfield"
|
||||
id="TextField0"
|
||||
type="text"
|
||||
value=""
|
||||
@@ -687,6 +697,7 @@ exports[`PreviewCopyJob should render with default state and empty job name 1`]
|
||||
</span>
|
||||
<span
|
||||
class="css-125"
|
||||
data-test="source-subscription-name"
|
||||
>
|
||||
Test Subscription
|
||||
</span>
|
||||
@@ -701,6 +712,7 @@ exports[`PreviewCopyJob should render with default state and empty job name 1`]
|
||||
</span>
|
||||
<span
|
||||
class="css-125"
|
||||
data-test="source-account-name"
|
||||
>
|
||||
test-account
|
||||
</span>
|
||||
@@ -957,6 +969,7 @@ exports[`PreviewCopyJob should render with default state and empty job name 1`]
|
||||
exports[`PreviewCopyJob should render with long subscription and account names 1`] = `
|
||||
<div
|
||||
class="ms-Stack previewCopyJobContainer css-109"
|
||||
data-test="Panel:PreviewCopyJob"
|
||||
>
|
||||
<div
|
||||
class="ms-Stack flex-row css-110"
|
||||
@@ -986,6 +999,7 @@ exports[`PreviewCopyJob should render with long subscription and account names 1
|
||||
<input
|
||||
aria-invalid="false"
|
||||
class="ms-TextField-field field-115"
|
||||
data-test="job-name-textfield"
|
||||
id="TextField60"
|
||||
type="text"
|
||||
value=""
|
||||
@@ -1005,6 +1019,7 @@ exports[`PreviewCopyJob should render with long subscription and account names 1
|
||||
</span>
|
||||
<span
|
||||
class="css-125"
|
||||
data-test="source-subscription-name"
|
||||
>
|
||||
This is a very long subscription name that might cause display issues if not handled properly
|
||||
</span>
|
||||
@@ -1019,6 +1034,7 @@ exports[`PreviewCopyJob should render with long subscription and account names 1
|
||||
</span>
|
||||
<span
|
||||
class="css-125"
|
||||
data-test="source-account-name"
|
||||
>
|
||||
this-is-a-very-long-database-account-name-that-might-cause-display-issues
|
||||
</span>
|
||||
@@ -1275,6 +1291,7 @@ exports[`PreviewCopyJob should render with long subscription and account names 1
|
||||
exports[`PreviewCopyJob should render with missing source account information 1`] = `
|
||||
<div
|
||||
class="ms-Stack previewCopyJobContainer css-109"
|
||||
data-test="Panel:PreviewCopyJob"
|
||||
>
|
||||
<div
|
||||
class="ms-Stack flex-row css-110"
|
||||
@@ -1304,6 +1321,7 @@ exports[`PreviewCopyJob should render with missing source account information 1`
|
||||
<input
|
||||
aria-invalid="false"
|
||||
class="ms-TextField-field field-115"
|
||||
data-test="job-name-textfield"
|
||||
id="TextField36"
|
||||
type="text"
|
||||
value=""
|
||||
@@ -1323,6 +1341,7 @@ exports[`PreviewCopyJob should render with missing source account information 1`
|
||||
</span>
|
||||
<span
|
||||
class="css-125"
|
||||
data-test="source-subscription-name"
|
||||
>
|
||||
Test Subscription
|
||||
</span>
|
||||
@@ -1588,6 +1607,7 @@ exports[`PreviewCopyJob should render with missing source account information 1`
|
||||
exports[`PreviewCopyJob should render with missing source subscription information 1`] = `
|
||||
<div
|
||||
class="ms-Stack previewCopyJobContainer css-109"
|
||||
data-test="Panel:PreviewCopyJob"
|
||||
>
|
||||
<div
|
||||
class="ms-Stack flex-row css-110"
|
||||
@@ -1617,6 +1637,7 @@ exports[`PreviewCopyJob should render with missing source subscription informati
|
||||
<input
|
||||
aria-invalid="false"
|
||||
class="ms-TextField-field field-115"
|
||||
data-test="job-name-textfield"
|
||||
id="TextField24"
|
||||
type="text"
|
||||
value=""
|
||||
@@ -1645,6 +1666,7 @@ exports[`PreviewCopyJob should render with missing source subscription informati
|
||||
</span>
|
||||
<span
|
||||
class="css-125"
|
||||
data-test="source-account-name"
|
||||
>
|
||||
test-account
|
||||
</span>
|
||||
@@ -1901,6 +1923,7 @@ exports[`PreviewCopyJob should render with missing source subscription informati
|
||||
exports[`PreviewCopyJob should render with online migration type 1`] = `
|
||||
<div
|
||||
class="ms-Stack previewCopyJobContainer css-109"
|
||||
data-test="Panel:PreviewCopyJob"
|
||||
>
|
||||
<div
|
||||
class="ms-Stack flex-row css-110"
|
||||
@@ -1930,6 +1953,7 @@ exports[`PreviewCopyJob should render with online migration type 1`] = `
|
||||
<input
|
||||
aria-invalid="false"
|
||||
class="ms-TextField-field field-115"
|
||||
data-test="job-name-textfield"
|
||||
id="TextField72"
|
||||
type="text"
|
||||
value="online-migration-job"
|
||||
@@ -1949,6 +1973,7 @@ exports[`PreviewCopyJob should render with online migration type 1`] = `
|
||||
</span>
|
||||
<span
|
||||
class="css-125"
|
||||
data-test="source-subscription-name"
|
||||
>
|
||||
Test Subscription
|
||||
</span>
|
||||
@@ -1963,6 +1988,7 @@ exports[`PreviewCopyJob should render with online migration type 1`] = `
|
||||
</span>
|
||||
<span
|
||||
class="css-125"
|
||||
data-test="source-account-name"
|
||||
>
|
||||
test-account
|
||||
</span>
|
||||
@@ -2219,6 +2245,7 @@ exports[`PreviewCopyJob should render with online migration type 1`] = `
|
||||
exports[`PreviewCopyJob should render with pre-filled job name 1`] = `
|
||||
<div
|
||||
class="ms-Stack previewCopyJobContainer css-109"
|
||||
data-test="Panel:PreviewCopyJob"
|
||||
>
|
||||
<div
|
||||
class="ms-Stack flex-row css-110"
|
||||
@@ -2248,6 +2275,7 @@ exports[`PreviewCopyJob should render with pre-filled job name 1`] = `
|
||||
<input
|
||||
aria-invalid="false"
|
||||
class="ms-TextField-field field-115"
|
||||
data-test="job-name-textfield"
|
||||
id="TextField12"
|
||||
type="text"
|
||||
value="custom-job-name-123"
|
||||
@@ -2267,6 +2295,7 @@ exports[`PreviewCopyJob should render with pre-filled job name 1`] = `
|
||||
</span>
|
||||
<span
|
||||
class="css-125"
|
||||
data-test="source-subscription-name"
|
||||
>
|
||||
Test Subscription
|
||||
</span>
|
||||
@@ -2281,6 +2310,7 @@ exports[`PreviewCopyJob should render with pre-filled job name 1`] = `
|
||||
</span>
|
||||
<span
|
||||
class="css-125"
|
||||
data-test="source-account-name"
|
||||
>
|
||||
test-account
|
||||
</span>
|
||||
@@ -2537,6 +2567,7 @@ exports[`PreviewCopyJob should render with pre-filled job name 1`] = `
|
||||
exports[`PreviewCopyJob should render with undefined database and container names 1`] = `
|
||||
<div
|
||||
class="ms-Stack previewCopyJobContainer css-109"
|
||||
data-test="Panel:PreviewCopyJob"
|
||||
>
|
||||
<div
|
||||
class="ms-Stack flex-row css-110"
|
||||
@@ -2566,6 +2597,7 @@ exports[`PreviewCopyJob should render with undefined database and container name
|
||||
<input
|
||||
aria-invalid="false"
|
||||
class="ms-TextField-field field-115"
|
||||
data-test="job-name-textfield"
|
||||
id="TextField48"
|
||||
type="text"
|
||||
value=""
|
||||
@@ -2585,6 +2617,7 @@ exports[`PreviewCopyJob should render with undefined database and container name
|
||||
</span>
|
||||
<span
|
||||
class="css-125"
|
||||
data-test="source-subscription-name"
|
||||
>
|
||||
Test Subscription
|
||||
</span>
|
||||
@@ -2599,6 +2632,7 @@ exports[`PreviewCopyJob should render with undefined database and container name
|
||||
</span>
|
||||
<span
|
||||
class="css-125"
|
||||
data-test="source-account-name"
|
||||
>
|
||||
test-account
|
||||
</span>
|
||||
|
||||
@@ -10,7 +10,7 @@ interface MigrationTypeCheckboxProps {
|
||||
}
|
||||
|
||||
export const MigrationTypeCheckbox: React.FC<MigrationTypeCheckboxProps> = React.memo(({ checked, onChange }) => (
|
||||
<Stack horizontal horizontalAlign="space-between" className="migrationTypeRow">
|
||||
<Stack horizontal horizontalAlign="space-between" className="migrationTypeRow" data-test="migration-type-checkbox">
|
||||
<Checkbox label={ContainerCopyMessages.migrationTypeCheckboxLabel} checked={checked} onChange={onChange} />
|
||||
</Stack>
|
||||
));
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
exports[`MigrationTypeCheckbox Component Rendering should render in checked state 1`] = `
|
||||
<div
|
||||
class="ms-Stack migrationTypeRow css-109"
|
||||
data-test="migration-type-checkbox"
|
||||
>
|
||||
<div
|
||||
class="ms-Checkbox is-checked is-enabled root-119"
|
||||
@@ -43,6 +44,7 @@ exports[`MigrationTypeCheckbox Component Rendering should render in checked stat
|
||||
exports[`MigrationTypeCheckbox Component Rendering should render with default props (unchecked state) 1`] = `
|
||||
<div
|
||||
class="ms-Stack migrationTypeRow css-109"
|
||||
data-test="migration-type-checkbox"
|
||||
>
|
||||
<div
|
||||
class="ms-Checkbox is-enabled root-110"
|
||||
|
||||
@@ -47,7 +47,11 @@ const SelectSourceAndTargetContainers = ({ showAddCollectionPanel }: SelectSourc
|
||||
const onDropdownChange = dropDownChangeHandler(setCopyJobState);
|
||||
|
||||
return (
|
||||
<Stack className="selectSourceAndTargetContainers" tokens={{ childrenGap: 25 }}>
|
||||
<Stack
|
||||
data-test="Panel:SelectSourceAndTargetContainers"
|
||||
className="selectSourceAndTargetContainers"
|
||||
tokens={{ childrenGap: 25 }}
|
||||
>
|
||||
<span>{ContainerCopyMessages.selectSourceAndTargetContainersDescription}</span>
|
||||
<DatabaseContainerSection
|
||||
heading={ContainerCopyMessages.sourceContainerSubHeading}
|
||||
@@ -59,6 +63,7 @@ const SelectSourceAndTargetContainers = ({ showAddCollectionPanel }: SelectSourc
|
||||
selectedContainer={source?.containerId}
|
||||
containerDisabled={!source?.databaseId}
|
||||
containerOnChange={onDropdownChange("sourceContainer")}
|
||||
sectionType="source"
|
||||
/>
|
||||
<DatabaseContainerSection
|
||||
heading={ContainerCopyMessages.targetContainerSubHeading}
|
||||
@@ -71,6 +76,7 @@ const SelectSourceAndTargetContainers = ({ showAddCollectionPanel }: SelectSourc
|
||||
containerDisabled={!target?.databaseId}
|
||||
containerOnChange={onDropdownChange("targetContainer")}
|
||||
handleOnDemandCreateContainer={showAddCollectionPanel}
|
||||
sectionType="target"
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
|
||||
@@ -32,6 +32,7 @@ describe("DatabaseContainerSection", () => {
|
||||
selectedContainer: "container1",
|
||||
containerDisabled: false,
|
||||
containerOnChange: mockContainerOnChange,
|
||||
sectionType: "source",
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -292,6 +293,7 @@ describe("DatabaseContainerSection", () => {
|
||||
containerOptions: mockContainerOptions,
|
||||
selectedContainer: "container1",
|
||||
containerOnChange: mockContainerOnChange,
|
||||
sectionType: "source",
|
||||
};
|
||||
|
||||
render(<DatabaseContainerSection {...minimalProps} />);
|
||||
@@ -393,6 +395,7 @@ describe("DatabaseContainerSection", () => {
|
||||
containerOptions: [{ key: "c1", text: "Container 1", data: { id: "c1" } }],
|
||||
selectedContainer: "c1",
|
||||
containerOnChange: jest.fn(),
|
||||
sectionType: "source",
|
||||
};
|
||||
|
||||
const { container } = render(<DatabaseContainerSection {...minimalProps} />);
|
||||
@@ -411,6 +414,7 @@ describe("DatabaseContainerSection", () => {
|
||||
containerDisabled: false,
|
||||
containerOnChange: jest.fn(),
|
||||
handleOnDemandCreateContainer: jest.fn(),
|
||||
sectionType: "target",
|
||||
};
|
||||
|
||||
const { container } = render(<DatabaseContainerSection {...fullProps} />);
|
||||
@@ -428,6 +432,7 @@ describe("DatabaseContainerSection", () => {
|
||||
selectedContainer: "container1",
|
||||
containerDisabled: true,
|
||||
containerOnChange: jest.fn(),
|
||||
sectionType: "target",
|
||||
};
|
||||
|
||||
const { container } = render(<DatabaseContainerSection {...disabledProps} />);
|
||||
@@ -443,6 +448,7 @@ describe("DatabaseContainerSection", () => {
|
||||
containerOptions: [],
|
||||
selectedContainer: "",
|
||||
containerOnChange: jest.fn(),
|
||||
sectionType: "target",
|
||||
};
|
||||
|
||||
const { container } = render(<DatabaseContainerSection {...emptyOptionsProps} />);
|
||||
|
||||
@@ -15,6 +15,7 @@ export const DatabaseContainerSection = ({
|
||||
containerDisabled,
|
||||
containerOnChange,
|
||||
handleOnDemandCreateContainer,
|
||||
sectionType,
|
||||
}: DatabaseContainerSectionProps) => (
|
||||
<Stack tokens={{ childrenGap: 15 }} className="databaseContainerSection">
|
||||
<label className="subHeading">{heading}</label>
|
||||
@@ -27,6 +28,7 @@ export const DatabaseContainerSection = ({
|
||||
disabled={!!databaseDisabled}
|
||||
selectedKey={selectedDatabase}
|
||||
onChange={databaseOnChange}
|
||||
data-test={`${sectionType}-databaseDropdown`}
|
||||
/>
|
||||
</FieldRow>
|
||||
<FieldRow label={ContainerCopyMessages.containerDropdownLabel}>
|
||||
@@ -39,6 +41,7 @@ export const DatabaseContainerSection = ({
|
||||
disabled={!!containerDisabled}
|
||||
selectedKey={selectedContainer}
|
||||
onChange={containerOnChange}
|
||||
data-test={`${sectionType}-containerDropdown`}
|
||||
/>
|
||||
{handleOnDemandCreateContainer && (
|
||||
<ActionButton className="create-container-link-btn" onClick={() => handleOnDemandCreateContainer()}>
|
||||
|
||||
@@ -37,6 +37,7 @@ exports[`DatabaseContainerSection Snapshot Testing matches snapshot with all pro
|
||||
class="ms-Dropdown is-required dropdown-112"
|
||||
data-is-focusable="true"
|
||||
data-ktp-target="true"
|
||||
data-test="target-databaseDropdown"
|
||||
id="Dropdown98"
|
||||
role="combobox"
|
||||
tabindex="0"
|
||||
@@ -94,6 +95,7 @@ exports[`DatabaseContainerSection Snapshot Testing matches snapshot with all pro
|
||||
class="ms-Dropdown is-required dropdown-112"
|
||||
data-is-focusable="true"
|
||||
data-ktp-target="true"
|
||||
data-test="target-containerDropdown"
|
||||
id="Dropdown99"
|
||||
role="combobox"
|
||||
tabindex="0"
|
||||
@@ -182,6 +184,7 @@ exports[`DatabaseContainerSection Snapshot Testing matches snapshot with disable
|
||||
class="ms-Dropdown is-disabled is-required dropdown-143"
|
||||
data-is-focusable="false"
|
||||
data-ktp-target="true"
|
||||
data-test="target-databaseDropdown"
|
||||
id="Dropdown103"
|
||||
role="combobox"
|
||||
tabindex="-1"
|
||||
@@ -239,6 +242,7 @@ exports[`DatabaseContainerSection Snapshot Testing matches snapshot with disable
|
||||
class="ms-Dropdown is-disabled is-required dropdown-143"
|
||||
data-is-focusable="false"
|
||||
data-ktp-target="true"
|
||||
data-test="target-containerDropdown"
|
||||
id="Dropdown104"
|
||||
role="combobox"
|
||||
tabindex="-1"
|
||||
@@ -306,6 +310,7 @@ exports[`DatabaseContainerSection Snapshot Testing matches snapshot with empty o
|
||||
class="ms-Dropdown is-required dropdown-112"
|
||||
data-is-focusable="true"
|
||||
data-ktp-target="true"
|
||||
data-test="target-databaseDropdown"
|
||||
id="Dropdown105"
|
||||
role="combobox"
|
||||
tabindex="0"
|
||||
@@ -363,6 +368,7 @@ exports[`DatabaseContainerSection Snapshot Testing matches snapshot with empty o
|
||||
class="ms-Dropdown is-required dropdown-112"
|
||||
data-is-focusable="true"
|
||||
data-ktp-target="true"
|
||||
data-test="target-containerDropdown"
|
||||
id="Dropdown106"
|
||||
role="combobox"
|
||||
tabindex="0"
|
||||
@@ -430,6 +436,7 @@ exports[`DatabaseContainerSection Snapshot Testing matches snapshot with minimal
|
||||
class="ms-Dropdown is-required dropdown-112"
|
||||
data-is-focusable="true"
|
||||
data-ktp-target="true"
|
||||
data-test="source-databaseDropdown"
|
||||
id="Dropdown96"
|
||||
role="combobox"
|
||||
tabindex="0"
|
||||
@@ -487,6 +494,7 @@ exports[`DatabaseContainerSection Snapshot Testing matches snapshot with minimal
|
||||
class="ms-Dropdown is-required dropdown-112"
|
||||
data-is-focusable="true"
|
||||
data-ktp-target="true"
|
||||
data-test="source-containerDropdown"
|
||||
id="Dropdown97"
|
||||
role="combobox"
|
||||
tabindex="0"
|
||||
|
||||
@@ -83,6 +83,7 @@ const CopyJobActionMenu: React.FC<CopyJobActionMenuProps> = ({ job, handleClick
|
||||
|
||||
return (
|
||||
<IconButton
|
||||
data-test={`CopyJobActionMenu/Button:${job.Name}`}
|
||||
role="button"
|
||||
iconProps={{ iconName: "More", styles: { root: { fontSize: "20px", fontWeight: "bold" } } }}
|
||||
menuProps={{ items: getMenuItems() }}
|
||||
|
||||
@@ -80,6 +80,7 @@ const CopyJobsList: React.FC<CopyJobsListProps> = ({ jobs, handleActionClick, pa
|
||||
<Stack.Item verticalFill={true} grow={1} shrink={1} style={styles.stackItem}>
|
||||
<ScrollablePane scrollbarVisibility={ScrollbarVisibility.auto}>
|
||||
<ShimmeredDetailsList
|
||||
className="CopyJobListContainer"
|
||||
onRenderRow={_onRenderRow}
|
||||
checkboxVisibility={2}
|
||||
columns={columns}
|
||||
|
||||
@@ -49,6 +49,7 @@ export interface DatabaseContainerSectionProps {
|
||||
containerDisabled?: boolean;
|
||||
containerOnChange: (ev: React.FormEvent<HTMLDivElement>, option: DropdownOptionType) => void;
|
||||
handleOnDemandCreateContainer?: () => void;
|
||||
sectionType: "source" | "target";
|
||||
}
|
||||
|
||||
export interface CopyJobContextState {
|
||||
|
||||
@@ -164,6 +164,9 @@ $ENV:NOSQL_TESTACCOUNT_TOKEN=az account get-access-token --scope "https://<accou
|
||||
# NoSQL API (Readonly)
|
||||
$ENV:NOSQL_READONLY_TESTACCOUNT_TOKEN=az account get-access-token --scope "https://<account name>.documents.azure.com/.default" -o tsv --query accessToken
|
||||
|
||||
# NoSQL API (Container Copy)
|
||||
$ENV:NOSQL_CONTAINERCOPY_TESTACCOUNT_TOKEN=az account get-access-token --scope "https://<account name>.documents.azure.com/.default" -o tsv --query accessToken
|
||||
|
||||
# Tables API
|
||||
$ENV:TABLE_TESTACCOUNT_TOKEN=az account get-access-token --scope "https://<account name>.documents.azure.com/.default" -o tsv --query accessToken
|
||||
|
||||
|
||||
118
test/fx.ts
118
test/fx.ts
@@ -11,7 +11,7 @@ export interface TestNameOptions {
|
||||
prefixed?: boolean;
|
||||
}
|
||||
|
||||
export function generateUniqueName(baseName, options?: TestNameOptions): string {
|
||||
export function generateUniqueName(baseName: string, options?: TestNameOptions): string {
|
||||
const length = options?.length ?? 1;
|
||||
const timestamp = options?.timestampped === undefined ? true : options.timestampped;
|
||||
const prefixed = options?.prefixed === undefined ? true : options.prefixed;
|
||||
@@ -40,6 +40,7 @@ export enum TestAccount {
|
||||
Mongo32 = "Mongo32",
|
||||
SQL = "SQL",
|
||||
SQLReadOnly = "SQLReadOnly",
|
||||
SQLContainerCopyOnly = "SQLContainerCopyOnly",
|
||||
}
|
||||
|
||||
export const defaultAccounts: Record<TestAccount, string> = {
|
||||
@@ -51,6 +52,7 @@ export const defaultAccounts: Record<TestAccount, string> = {
|
||||
[TestAccount.Mongo32]: "github-e2etests-mongo32",
|
||||
[TestAccount.SQL]: "github-e2etests-sql",
|
||||
[TestAccount.SQLReadOnly]: "github-e2etests-sql-readonly",
|
||||
[TestAccount.SQLContainerCopyOnly]: "github-e2etests-sql-containercopyonly",
|
||||
};
|
||||
|
||||
export const resourceGroupName = process.env.DE_TEST_RESOURCE_GROUP ?? "de-e2e-tests";
|
||||
@@ -77,7 +79,14 @@ export function getAccountName(accountType: TestAccount) {
|
||||
);
|
||||
}
|
||||
|
||||
export async function getTestExplorerUrl(accountType: TestAccount, iframeSrc?: string): Promise<string> {
|
||||
type TestExplorerUrlOptions = {
|
||||
iframeSrc?: string;
|
||||
enablecontainercopy?: boolean;
|
||||
};
|
||||
|
||||
export async function getTestExplorerUrl(accountType: TestAccount, options?: TestExplorerUrlOptions): Promise<string> {
|
||||
const { iframeSrc, enablecontainercopy } = options ?? {};
|
||||
|
||||
// We can't retrieve AZ CLI credentials from the browser so we get them here.
|
||||
const token = await getAzureCLICredentialsToken();
|
||||
const accountName = getAccountName(accountType);
|
||||
@@ -93,6 +102,7 @@ export async function getTestExplorerUrl(accountType: TestAccount, iframeSrc?: s
|
||||
|
||||
const nosqlRbacToken = process.env.NOSQL_TESTACCOUNT_TOKEN;
|
||||
const nosqlReadOnlyRbacToken = process.env.NOSQL_READONLY_TESTACCOUNT_TOKEN;
|
||||
const nosqlContainerCopyRbacToken = process.env.NOSQL_CONTAINERCOPY_TESTACCOUNT_TOKEN;
|
||||
const tableRbacToken = process.env.TABLE_TESTACCOUNT_TOKEN;
|
||||
const gremlinRbacToken = process.env.GREMLIN_TESTACCOUNT_TOKEN;
|
||||
const cassandraRbacToken = process.env.CASSANDRA_TESTACCOUNT_TOKEN;
|
||||
@@ -108,6 +118,16 @@ export async function getTestExplorerUrl(accountType: TestAccount, iframeSrc?: s
|
||||
}
|
||||
break;
|
||||
|
||||
case TestAccount.SQLContainerCopyOnly:
|
||||
if (nosqlContainerCopyRbacToken) {
|
||||
params.set("nosqlRbacToken", nosqlContainerCopyRbacToken);
|
||||
params.set("enableaaddataplane", "true");
|
||||
}
|
||||
if (enablecontainercopy) {
|
||||
params.set("enablecontainercopy", "true");
|
||||
}
|
||||
break;
|
||||
|
||||
case TestAccount.SQLReadOnly:
|
||||
if (nosqlReadOnlyRbacToken) {
|
||||
params.set("nosqlReadOnlyRbacToken", nosqlReadOnlyRbacToken);
|
||||
@@ -490,8 +510,100 @@ export class DataExplorer {
|
||||
|
||||
/** Opens the Data Explorer app using the specified test account (and optionally, the provided IFRAME src url). */
|
||||
static async open(page: Page, testAccount: TestAccount, iframeSrc?: string): Promise<DataExplorer> {
|
||||
const url = await getTestExplorerUrl(testAccount, iframeSrc);
|
||||
const url = await getTestExplorerUrl(testAccount, { iframeSrc });
|
||||
await page.goto(url);
|
||||
return DataExplorer.waitForExplorer(page);
|
||||
}
|
||||
}
|
||||
|
||||
export class ContainerCopy {
|
||||
constructor(
|
||||
public frame: Frame,
|
||||
public wrapper: Locator,
|
||||
) {}
|
||||
|
||||
static async waitForContainerCopy(page: Page): Promise<ContainerCopy> {
|
||||
const explorerFrame = await DataExplorer.waitForExplorer(page);
|
||||
const containerCopyWrapper = explorerFrame.frame.locator("div#containerCopyWrapper");
|
||||
return new ContainerCopy(explorerFrame.frame, containerCopyWrapper);
|
||||
}
|
||||
|
||||
static async open(page: Page, testAccount: TestAccount, iframeSrc?: string): Promise<ContainerCopy> {
|
||||
const url = await getTestExplorerUrl(testAccount, { iframeSrc, enablecontainercopy: true });
|
||||
await page.goto(url);
|
||||
return ContainerCopy.waitForContainerCopy(page);
|
||||
}
|
||||
static async waitForApiResponse(
|
||||
page: Page,
|
||||
urlPattern: string,
|
||||
method?: string,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
payloadValidator?: (payload: any) => boolean,
|
||||
) {
|
||||
return page.waitForResponse(async (response) => {
|
||||
const request = response.request();
|
||||
|
||||
if (!request.url().includes(urlPattern)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (method && request.method() !== method) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (payloadValidator && (request.method() === "POST" || request.method() === "PUT")) {
|
||||
const postData = request.postData();
|
||||
if (postData) {
|
||||
try {
|
||||
const payload = JSON.parse(postData);
|
||||
return payloadValidator(payload);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
static async interceptAndInspectApiRequest(
|
||||
page: Page,
|
||||
urlPattern: string,
|
||||
method: string = "POST",
|
||||
error: Error,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
errorValidator: (url?: string, payload?: any) => boolean,
|
||||
): Promise<void> {
|
||||
await page.route(
|
||||
(url) => url.pathname.includes(urlPattern),
|
||||
async (route, request) => {
|
||||
if (request.method() !== method) {
|
||||
await route.continue();
|
||||
return;
|
||||
}
|
||||
const postData = request.postData();
|
||||
if (postData) {
|
||||
try {
|
||||
const payload = JSON.parse(postData);
|
||||
if (errorValidator && errorValidator(request.url(), payload)) {
|
||||
await route.fulfill({
|
||||
status: 409,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({
|
||||
code: "Conflict",
|
||||
message: error.message,
|
||||
}),
|
||||
});
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
if (err instanceof Error && err.message.includes("not allowed")) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await route.continue();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
649
test/sql/containercopy.spec.ts
Normal file
649
test/sql/containercopy.spec.ts
Normal file
@@ -0,0 +1,649 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { expect, Frame, Locator, Page, test } from "@playwright/test";
|
||||
import { set } from "lodash";
|
||||
import { DatabaseAccount, Subscription } from "../../src/Contracts/DataModels";
|
||||
import { truncateName } from "../../src/Explorer/ContainerCopy/CopyJobUtils";
|
||||
import { ContainerCopy, getAccountName, TestAccount } from "../fx";
|
||||
|
||||
test.describe.configure({ mode: "serial" });
|
||||
let page: Page;
|
||||
let wrapper: Locator = null!;
|
||||
let panel: Locator = null!;
|
||||
let frame: Frame = null!;
|
||||
let expectedCopyJobNameInitial: string = null!;
|
||||
let expectedSourceSubscription: any = null!;
|
||||
let expectedSourceAccount: DatabaseAccount = null!;
|
||||
let expectedJobName: string = "";
|
||||
let targetAccountName: string = "";
|
||||
let expectedSourceAccountName: string = "";
|
||||
|
||||
test.beforeAll("Container Copy - Before All", async ({ browser }) => {
|
||||
page = await browser.newPage();
|
||||
({ wrapper, frame } = await ContainerCopy.open(page, TestAccount.SQLContainerCopyOnly));
|
||||
expectedJobName = `test_job_${Date.now()}`;
|
||||
targetAccountName = getAccountName(TestAccount.SQLContainerCopyOnly);
|
||||
});
|
||||
|
||||
test.afterEach("Container Copy - After Each", async () => {
|
||||
await page.unroute(/.*/, (route) => route.continue());
|
||||
});
|
||||
|
||||
test("Loading and verifying the content of the page", async () => {
|
||||
expect(wrapper).not.toBeNull();
|
||||
await expect(wrapper.getByTestId("CommandBar/Button:Create Copy Job")).toBeVisible();
|
||||
await expect(wrapper.getByTestId("CommandBar/Button:Refresh")).toBeVisible();
|
||||
await expect(wrapper.getByTestId("CommandBar/Button:Feedback")).toBeVisible();
|
||||
});
|
||||
|
||||
test("Opening the Create Copy Job panel", async () => {
|
||||
const createCopyJobButton = wrapper.getByTestId("CommandBar/Button:Create Copy Job");
|
||||
await createCopyJobButton.click();
|
||||
panel = frame.getByTestId("Panel:Create copy job");
|
||||
await expect(panel).toBeVisible();
|
||||
await expect(panel.getByRole("heading", { name: "Create copy job" })).toBeVisible();
|
||||
});
|
||||
|
||||
test("select different account dropdown", async () => {
|
||||
const accountDropdown = panel.getByTestId("account-dropdown");
|
||||
await accountDropdown.click();
|
||||
|
||||
const dropdownItemsWrapper = frame.locator("div.ms-Dropdown-items");
|
||||
expect(await dropdownItemsWrapper.getAttribute("aria-label")).toEqual("Account");
|
||||
|
||||
const allDropdownItems = await dropdownItemsWrapper.locator(`button.ms-Dropdown-item[role='option']`).all();
|
||||
|
||||
const filteredItems = [];
|
||||
for (const item of allDropdownItems) {
|
||||
const testContent = (await item.textContent()) ?? "";
|
||||
if (testContent.trim() !== targetAccountName.trim()) {
|
||||
filteredItems.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
if (filteredItems.length > 0) {
|
||||
const firstDropdownItem = filteredItems[0];
|
||||
expectedSourceAccountName = (await firstDropdownItem.textContent()) ?? "";
|
||||
await firstDropdownItem.click();
|
||||
} else {
|
||||
throw new Error("No dropdown items available after filtering");
|
||||
}
|
||||
|
||||
const fluentUiCheckboxContainer = panel.getByTestId("migration-type-checkbox").locator("div.ms-Checkbox");
|
||||
await fluentUiCheckboxContainer.click();
|
||||
|
||||
await panel.getByRole("button", { name: "Next" }).click();
|
||||
});
|
||||
|
||||
test("Verifying Assign Permissions panel for online copy", async () => {
|
||||
const permissionScreen = panel.getByTestId("Panel:AssignPermissionsContainer");
|
||||
await expect(permissionScreen).toBeVisible();
|
||||
|
||||
await expect(permissionScreen.getByText("Online container copy", { exact: true })).toBeVisible();
|
||||
await expect(permissionScreen.getByText("Cross-account container copy", { exact: true })).toBeVisible();
|
||||
});
|
||||
|
||||
test("Verify Point-in-Time Restore timer and refresh button workflow", async () => {
|
||||
await page.route(`**/Microsoft.DocumentDB/databaseAccounts/${expectedSourceAccountName}**`, async (route) => {
|
||||
const mockData = {
|
||||
identity: {
|
||||
type: "SystemAssigned",
|
||||
principalId: "00-11-22-33",
|
||||
},
|
||||
properties: {
|
||||
defaultIdentity: "SystemAssignedIdentity",
|
||||
backupPolicy: {
|
||||
type: "Continuous",
|
||||
},
|
||||
capabilities: [{ name: "EnableOnlineContainerCopy" }],
|
||||
},
|
||||
};
|
||||
if (route.request().method() === "GET") {
|
||||
const response = await route.fetch();
|
||||
const actualData = await response.json();
|
||||
const mergedData = { ...actualData };
|
||||
|
||||
set(mergedData, "identity", mockData.identity);
|
||||
set(mergedData, "properties.defaultIdentity", mockData.properties.defaultIdentity);
|
||||
set(mergedData, "properties.backupPolicy", mockData.properties.backupPolicy);
|
||||
set(mergedData, "properties.capabilities", mockData.properties.capabilities);
|
||||
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify(mergedData),
|
||||
});
|
||||
} else {
|
||||
await route.continue();
|
||||
}
|
||||
});
|
||||
|
||||
const permissionScreen = panel.getByTestId("Panel:AssignPermissionsContainer");
|
||||
await expect(permissionScreen).toBeVisible();
|
||||
|
||||
const expandedAccordionHeader = permissionScreen
|
||||
.getByTestId("permission-group-container-onlineConfigs")
|
||||
.locator("button[aria-expanded='true']");
|
||||
await expect(expandedAccordionHeader).toBeVisible();
|
||||
|
||||
const accordionItem = expandedAccordionHeader
|
||||
.locator("xpath=ancestor::*[contains(@class, 'fui-AccordionItem') or contains(@data-test, 'accordion-item')]")
|
||||
.first();
|
||||
|
||||
const accordionPanel = accordionItem.locator("[role='tabpanel'], .fui-AccordionPanel, [data-test*='panel']").first();
|
||||
|
||||
await page.clock.install({ time: new Date("2024-01-01T10:00:00Z") });
|
||||
|
||||
const pitrBtn = accordionPanel.getByTestId("pointInTimeRestore:PrimaryBtn");
|
||||
await expect(pitrBtn).toBeVisible();
|
||||
await pitrBtn.click();
|
||||
|
||||
page.context().on("page", async (newPage) => {
|
||||
expect(newPage.url()).toMatch(
|
||||
new RegExp(`/providers/Microsoft\\.Document[DB][Db]/databaseAccounts/${expectedSourceAccountName}/backupRestore`),
|
||||
);
|
||||
await newPage.close();
|
||||
});
|
||||
|
||||
const loadingOverlay = frame.locator("[data-test='loading-overlay']");
|
||||
await expect(loadingOverlay).toBeVisible();
|
||||
|
||||
const refreshBtn = accordionPanel.getByTestId("pointInTimeRestore:RefreshBtn");
|
||||
await expect(refreshBtn).not.toBeVisible();
|
||||
|
||||
// Fast forward time by 11 minutes (11 * 60 * 1000ms = 660000ms)
|
||||
await page.clock.fastForward(11 * 60 * 1000);
|
||||
|
||||
await expect(refreshBtn).toBeVisible();
|
||||
await expect(pitrBtn).not.toBeVisible();
|
||||
|
||||
await refreshBtn.click();
|
||||
await expect(loadingOverlay).toBeVisible();
|
||||
|
||||
await expect(loadingOverlay).toBeHidden({ timeout: 10 * 1000 });
|
||||
});
|
||||
|
||||
test("Veify Popover & Loading Overlay on permission screen with API mocks and accordion interactions", async () => {
|
||||
await page.route(
|
||||
`**/Microsoft.DocumentDB/databaseAccounts/${expectedSourceAccountName}/sqlRoleAssignments*`,
|
||||
async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({
|
||||
value: [
|
||||
{
|
||||
principalId: "00-11-22-33",
|
||||
roleDefinitionId: `Microsoft.DocumentDB/databaseAccounts/${expectedSourceAccountName}/77-88-99`,
|
||||
},
|
||||
],
|
||||
}),
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
await page.route("**/Microsoft.DocumentDB/databaseAccounts/*/77-88-99**", async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({
|
||||
value: [
|
||||
{
|
||||
name: "00000000-0000-0000-0000-000000000001",
|
||||
},
|
||||
],
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
await page.route(`**/Microsoft.DocumentDB/databaseAccounts/${targetAccountName}**`, async (route) => {
|
||||
const mockData = {
|
||||
identity: {
|
||||
type: "SystemAssigned",
|
||||
principalId: "00-11-22-33",
|
||||
},
|
||||
properties: {
|
||||
defaultIdentity: "SystemAssignedIdentity",
|
||||
backupPolicy: {
|
||||
type: "Continuous",
|
||||
},
|
||||
capabilities: [{ name: "EnableOnlineContainerCopy" }],
|
||||
},
|
||||
};
|
||||
|
||||
if (route.request().method() === "PATCH") {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({ status: "Succeeded" }),
|
||||
});
|
||||
} else if (route.request().method() === "GET") {
|
||||
// Get the actual response and merge with mock data
|
||||
const response = await route.fetch();
|
||||
const actualData = await response.json();
|
||||
const mergedData = { ...actualData };
|
||||
set(mergedData, "identity", mockData.identity);
|
||||
set(mergedData, "properties.defaultIdentity", mockData.properties.defaultIdentity);
|
||||
set(mergedData, "properties.backupPolicy", mockData.properties.backupPolicy);
|
||||
set(mergedData, "properties.capabilities", mockData.properties.capabilities);
|
||||
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify(mergedData),
|
||||
});
|
||||
} else {
|
||||
await route.continue();
|
||||
}
|
||||
});
|
||||
|
||||
const permissionScreen = panel.getByTestId("Panel:AssignPermissionsContainer");
|
||||
await expect(permissionScreen).toBeVisible();
|
||||
|
||||
const expandedAccordionHeader = permissionScreen
|
||||
.getByTestId("permission-group-container-crossAccountConfigs")
|
||||
.locator("button[aria-expanded='true']");
|
||||
await expect(expandedAccordionHeader).toBeVisible();
|
||||
|
||||
const accordionItem = expandedAccordionHeader
|
||||
.locator("xpath=ancestor::*[contains(@class, 'fui-AccordionItem') or contains(@data-test, 'accordion-item')]")
|
||||
.first();
|
||||
|
||||
const accordionPanel = accordionItem.locator("[role='tabpanel'], .fui-AccordionPanel, [data-test*='panel']").first();
|
||||
|
||||
const toggleButton = accordionPanel.getByTestId("btn-toggle");
|
||||
await expect(toggleButton).toBeVisible();
|
||||
await toggleButton.click();
|
||||
|
||||
const popover = frame.locator("[data-test='popover-container']");
|
||||
await expect(popover).toBeVisible();
|
||||
|
||||
const yesButton = popover.getByRole("button", { name: /Yes/i });
|
||||
const noButton = popover.getByRole("button", { name: /No/i });
|
||||
await expect(yesButton).toBeVisible();
|
||||
await expect(noButton).toBeVisible();
|
||||
|
||||
await yesButton.click();
|
||||
|
||||
const loadingOverlay = frame.locator("[data-test='loading-overlay']");
|
||||
await expect(loadingOverlay).toBeVisible();
|
||||
|
||||
await expect(loadingOverlay).toBeHidden({ timeout: 10 * 1000 });
|
||||
await expect(popover).toBeHidden({ timeout: 10 * 1000 });
|
||||
|
||||
await panel.getByRole("button", { name: "Cancel" }).click();
|
||||
});
|
||||
|
||||
test("Loading and verifying subscription & account dropdown", async () => {
|
||||
const createCopyJobButton = wrapper.getByTestId("CommandBar/Button:Create Copy Job");
|
||||
await createCopyJobButton.click();
|
||||
panel = frame.getByTestId("Panel:Create copy job");
|
||||
await expect(panel).toBeVisible();
|
||||
|
||||
const subscriptionPromise = ContainerCopy.waitForApiResponse(
|
||||
page,
|
||||
"/Microsoft.ResourceGraph/resources",
|
||||
"POST",
|
||||
(payload: any) => {
|
||||
return (
|
||||
payload.query.includes("resources | where type == 'microsoft.documentdb/databaseaccounts'") &&
|
||||
payload.query.includes("| where type == 'microsoft.resources/subscriptions'")
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
const accountPromise = ContainerCopy.waitForApiResponse(
|
||||
page,
|
||||
"/Microsoft.ResourceGraph/resources",
|
||||
"POST",
|
||||
(payload: any) => {
|
||||
return payload.query.includes("resources | where type =~ 'microsoft.documentdb/databaseaccounts'");
|
||||
},
|
||||
);
|
||||
|
||||
const subscriptionResponse = await subscriptionPromise;
|
||||
const data = await subscriptionResponse.json();
|
||||
expect(subscriptionResponse.ok()).toBe(true);
|
||||
|
||||
const accountResponse = await accountPromise;
|
||||
const accountData = await accountResponse.json();
|
||||
expect(accountResponse.ok()).toBe(true);
|
||||
|
||||
const selectedSubscription = data.data.find(
|
||||
(item: Subscription) => item.subscriptionId === process.env.DE_TEST_SUBSCRIPTION_ID,
|
||||
);
|
||||
|
||||
const subscriptionDropdown = panel.getByTestId("subscription-dropdown");
|
||||
await expect(subscriptionDropdown).toHaveText(new RegExp(selectedSubscription.subscriptionName));
|
||||
await subscriptionDropdown.click();
|
||||
|
||||
const dropdownItemsWrapper = frame.locator("div.ms-Dropdown-items");
|
||||
expect(await dropdownItemsWrapper.getAttribute("aria-label")).toEqual("Subscription");
|
||||
const subscriptionDropdownItems = dropdownItemsWrapper.locator("button.ms-Dropdown-item[role='option']");
|
||||
expect(subscriptionDropdownItems).toHaveCount(data.count);
|
||||
const subscriptionItem = subscriptionDropdownItems.filter({ hasText: selectedSubscription.subscriptionName });
|
||||
await subscriptionItem.click();
|
||||
|
||||
const expectedAccountName = getAccountName(TestAccount.SQLContainerCopyOnly);
|
||||
const selectedAccount = accountData.data.find((item: DatabaseAccount) => item.name === expectedAccountName);
|
||||
|
||||
const accountDropdown = panel.getByTestId("account-dropdown");
|
||||
await expect(accountDropdown).toHaveText(new RegExp(expectedAccountName));
|
||||
await accountDropdown.click();
|
||||
|
||||
expect(await dropdownItemsWrapper.getAttribute("aria-label")).toEqual("Account");
|
||||
const accountDropdownItemCount = await dropdownItemsWrapper.locator("button.ms-Dropdown-item[role='option']").count();
|
||||
expect(accountDropdownItemCount).toBeLessThanOrEqual(accountData.count);
|
||||
|
||||
await dropdownItemsWrapper
|
||||
.locator("button.ms-Dropdown-item[role='option']", { hasText: expectedAccountName })
|
||||
.click();
|
||||
|
||||
expectedSourceSubscription = selectedSubscription;
|
||||
expectedSourceAccount = selectedAccount;
|
||||
});
|
||||
|
||||
test("Verifying online or offline checkbox", async () => {
|
||||
/**
|
||||
* This test verifies the functionality of the migration type checkbox that toggles between
|
||||
* online and offline container copy modes. It ensures that:
|
||||
* 1. When online mode is selected, the user is directed to a permissions screen
|
||||
* 2. When offline mode is selected, the user bypasses the permissions screen
|
||||
* 3. The UI correctly reflects the selected migration type throughout the workflow
|
||||
*/
|
||||
const fluentUiCheckboxContainer = panel.getByTestId("migration-type-checkbox").locator("div.ms-Checkbox");
|
||||
await fluentUiCheckboxContainer.click();
|
||||
await panel.getByRole("button", { name: "Next" }).click();
|
||||
await expect(panel.getByTestId("Panel:AssignPermissionsContainer")).toBeVisible();
|
||||
await expect(panel.getByText("Online container copy", { exact: true })).toBeVisible();
|
||||
await panel.getByRole("button", { name: "Previous" }).click();
|
||||
await fluentUiCheckboxContainer.click();
|
||||
await panel.getByRole("button", { name: "Next" }).click();
|
||||
await expect(panel.getByTestId("Panel:SelectSourceAndTargetContainers")).toBeVisible();
|
||||
await expect(panel.getByTestId("Panel:AssignPermissionsContainer")).not.toBeVisible();
|
||||
});
|
||||
|
||||
test("Verifying source and target container selection", async () => {
|
||||
const sourceContainerDropdown = panel.getByTestId("source-containerDropdown");
|
||||
expect(sourceContainerDropdown).toBeVisible();
|
||||
await expect(sourceContainerDropdown).toHaveClass(/(^|\s)is-disabled(\s|$)/);
|
||||
|
||||
const sourceDatabaseDropdown = panel.getByTestId("source-databaseDropdown");
|
||||
await sourceDatabaseDropdown.click();
|
||||
const dropdownItemsWrapper = frame.locator("div.ms-Dropdown-items");
|
||||
expect(await dropdownItemsWrapper.getAttribute("aria-label")).toEqual("Database");
|
||||
const sourceDbDropdownItems = dropdownItemsWrapper.locator("button.ms-Dropdown-item[role='option']");
|
||||
await sourceDbDropdownItems.first().click();
|
||||
await expect(sourceContainerDropdown).not.toHaveClass(/(^|\s)is-disabled(\s|$)/);
|
||||
await sourceContainerDropdown.click();
|
||||
expect(await dropdownItemsWrapper.getAttribute("aria-label")).toEqual("Container");
|
||||
const sourceContainerDropdownItems = dropdownItemsWrapper.locator("button.ms-Dropdown-item[role='option']");
|
||||
await sourceContainerDropdownItems.first().click();
|
||||
|
||||
const targetContainerDropdown = panel.getByTestId("target-containerDropdown");
|
||||
expect(targetContainerDropdown).toBeVisible();
|
||||
await expect(targetContainerDropdown).toHaveClass(/(^|\s)is-disabled(\s|$)/);
|
||||
|
||||
const targetDatabaseDropdown = panel.getByTestId("target-databaseDropdown");
|
||||
await targetDatabaseDropdown.click();
|
||||
expect(await dropdownItemsWrapper.getAttribute("aria-label")).toEqual("Database");
|
||||
const targetDbDropdownItems = dropdownItemsWrapper.locator("button.ms-Dropdown-item[role='option']");
|
||||
await targetDbDropdownItems.first().click();
|
||||
await expect(targetContainerDropdown).not.toHaveClass(/(^|\s)is-disabled(\s|$)/);
|
||||
await targetContainerDropdown.click();
|
||||
expect(await dropdownItemsWrapper.getAttribute("aria-label")).toEqual("Container");
|
||||
const targetContainerDropdownItems = dropdownItemsWrapper.locator("button.ms-Dropdown-item[role='option']");
|
||||
await targetContainerDropdownItems.first().click();
|
||||
|
||||
await panel.getByRole("button", { name: "Next" }).click();
|
||||
|
||||
const errorContainer = panel.getByTestId("Panel:ErrorContainer");
|
||||
await expect(errorContainer).toBeVisible();
|
||||
await expect(errorContainer).toHaveText(/Source and destination containers cannot be the same/i);
|
||||
await targetContainerDropdown.click();
|
||||
await targetContainerDropdownItems.nth(1).click();
|
||||
|
||||
const selectedSourceDatabase = await sourceDatabaseDropdown.innerText();
|
||||
const selectedSourceContainer = await sourceContainerDropdown.innerText();
|
||||
const selectedTargetDatabase = await targetDatabaseDropdown.innerText();
|
||||
const selectedTargetContainer = await targetContainerDropdown.innerText();
|
||||
expectedCopyJobNameInitial = `${truncateName(selectedSourceDatabase)}.${truncateName(
|
||||
selectedSourceContainer,
|
||||
)}_${truncateName(selectedTargetDatabase)}.${truncateName(selectedTargetContainer)}`;
|
||||
|
||||
await panel.getByRole("button", { name: "Next" }).click();
|
||||
|
||||
await expect(errorContainer).not.toBeVisible();
|
||||
await expect(panel.getByTestId("Panel:PreviewCopyJob")).toBeVisible();
|
||||
});
|
||||
|
||||
test("Verifying the preview of the copy job", async () => {
|
||||
const previewContainer = panel.getByTestId("Panel:PreviewCopyJob");
|
||||
await expect(previewContainer).toBeVisible();
|
||||
await expect(previewContainer.getByTestId("source-subscription-name")).toHaveText(
|
||||
expectedSourceSubscription.subscriptionName,
|
||||
);
|
||||
await expect(previewContainer.getByTestId("source-account-name")).toHaveText(expectedSourceAccount.name);
|
||||
const jobNameInput = previewContainer.getByTestId("job-name-textfield");
|
||||
await expect(jobNameInput).toHaveValue(new RegExp(expectedCopyJobNameInitial));
|
||||
const primaryBtn = panel.getByRole("button", { name: "Copy", exact: true });
|
||||
await expect(primaryBtn).not.toHaveClass(/(^|\s)is-disabled(\s|$)/);
|
||||
|
||||
await jobNameInput.fill("test job name");
|
||||
await expect(primaryBtn).toHaveClass(/(^|\s)is-disabled(\s|$)/);
|
||||
});
|
||||
|
||||
test("Testing API request interception with duplicate job name", async () => {
|
||||
const previewContainer = panel.getByTestId("Panel:PreviewCopyJob");
|
||||
const jobNameInput = previewContainer.getByTestId("job-name-textfield");
|
||||
const duplicateJobName = "test-job-name-1";
|
||||
await jobNameInput.fill(duplicateJobName);
|
||||
|
||||
const copyButton = panel.getByRole("button", { name: "Copy", exact: true });
|
||||
const expectedErrorMessage = `Duplicate job name '${duplicateJobName}'`;
|
||||
await ContainerCopy.interceptAndInspectApiRequest(
|
||||
page,
|
||||
`${expectedSourceAccount.name}/dataTransferJobs/${duplicateJobName}`,
|
||||
"PUT",
|
||||
new Error(expectedErrorMessage),
|
||||
(url?: string) => url?.includes(duplicateJobName) ?? false,
|
||||
);
|
||||
|
||||
let errorThrown = false;
|
||||
try {
|
||||
await copyButton.click();
|
||||
await page.waitForTimeout(2000);
|
||||
} catch (error: any) {
|
||||
errorThrown = true;
|
||||
expect(error.message).toContain("not allowed");
|
||||
}
|
||||
if (!errorThrown) {
|
||||
const errorContainer = panel.getByTestId("Panel:ErrorContainer");
|
||||
await expect(errorContainer).toBeVisible();
|
||||
await expect(errorContainer).toHaveText(new RegExp(expectedErrorMessage, "i"));
|
||||
}
|
||||
|
||||
await expect(panel).toBeVisible();
|
||||
});
|
||||
|
||||
test("Testing API request success with valid job name and verifying copy job creation", async () => {
|
||||
const previewContainer = panel.getByTestId("Panel:PreviewCopyJob");
|
||||
const jobNameInput = previewContainer.getByTestId("job-name-textfield");
|
||||
const copyButton = panel.getByRole("button", { name: "Copy", exact: true });
|
||||
|
||||
const validJobName = expectedJobName;
|
||||
|
||||
const copyJobCreationPromise = ContainerCopy.waitForApiResponse(
|
||||
page,
|
||||
`${expectedSourceAccount.name}/dataTransferJobs/${validJobName}`,
|
||||
"PUT",
|
||||
);
|
||||
|
||||
await jobNameInput.fill(validJobName);
|
||||
await expect(copyButton).not.toHaveClass(/(^|\s)is-disabled(\s|$)/);
|
||||
|
||||
await copyButton.click();
|
||||
|
||||
const response = await copyJobCreationPromise;
|
||||
expect(response.ok()).toBe(true);
|
||||
|
||||
await expect(panel).not.toBeVisible({ timeout: 10000 });
|
||||
|
||||
const jobsListContainer = wrapper.locator(".CopyJobListContainer .ms-DetailsList-contentWrapper .ms-List-page");
|
||||
await jobsListContainer.waitFor({ state: "visible" });
|
||||
|
||||
const jobItem = jobsListContainer.getByText(validJobName);
|
||||
await jobItem.waitFor({ state: "visible" });
|
||||
await expect(jobItem).toBeVisible();
|
||||
});
|
||||
|
||||
test.skip("Pause a running copy job", async () => {
|
||||
const jobsListContainer = wrapper.locator(".CopyJobListContainer .ms-DetailsList-contentWrapper .ms-List-page");
|
||||
await jobsListContainer.waitFor({ state: "visible" });
|
||||
|
||||
const firstJobRow = jobsListContainer.locator(".ms-DetailsRow", { hasText: expectedJobName });
|
||||
await firstJobRow.waitFor({ state: "visible" });
|
||||
|
||||
const actionMenuButton = wrapper.getByTestId(`CopyJobActionMenu/Button:${expectedJobName}`);
|
||||
await actionMenuButton.waitFor({ state: "visible" });
|
||||
await actionMenuButton.click();
|
||||
|
||||
const pauseAction = frame.locator(".ms-ContextualMenu-list button:has-text('Pause')");
|
||||
await pauseAction.waitFor({ state: "visible" });
|
||||
await pauseAction.click();
|
||||
|
||||
const updatedJobRow = jobsListContainer.locator(".ms-DetailsRow").filter({ hasText: expectedJobName });
|
||||
const statusCell = updatedJobRow.locator("[data-automationid='DetailsRowCell'][data-automation-key='CopyJobStatus']");
|
||||
await expect(statusCell).toContainText(/paused/i, { timeout: 10000 });
|
||||
});
|
||||
|
||||
test.skip("Resume a paused copy job", async () => {
|
||||
const jobsListContainer = wrapper.locator(".CopyJobListContainer .ms-DetailsList-contentWrapper .ms-List-page");
|
||||
await jobsListContainer.waitFor({ state: "visible" });
|
||||
|
||||
const pausedJobRow = jobsListContainer.locator(".ms-DetailsRow", { hasText: expectedJobName });
|
||||
await pausedJobRow.waitFor({ state: "visible" });
|
||||
|
||||
const statusCell = pausedJobRow.locator("[data-automationid='DetailsRowCell'][data-automation-key='CopyJobStatus']");
|
||||
await expect(statusCell).toContainText(/paused/i);
|
||||
|
||||
const actionMenuButton = wrapper.getByTestId(`CopyJobActionMenu/Button:${expectedJobName}`);
|
||||
await actionMenuButton.waitFor({ state: "visible" });
|
||||
await actionMenuButton.click();
|
||||
|
||||
const resumeAction = frame.locator(".ms-ContextualMenu-list button:has-text('Resume')");
|
||||
await resumeAction.waitFor({ state: "visible" });
|
||||
await resumeAction.click();
|
||||
|
||||
await expect(statusCell).toContainText(/running|queued/i);
|
||||
});
|
||||
|
||||
test("Cancel a copy job", async () => {
|
||||
// Create a new job specifically for cancellation testing
|
||||
const cancelJobName = `cancel_test_job_${Date.now()}`;
|
||||
|
||||
// Navigate to create job panel
|
||||
const createCopyJobButton = wrapper.getByTestId("CommandBar/Button:Create Copy Job");
|
||||
await createCopyJobButton.click();
|
||||
panel = frame.getByTestId("Panel:Create copy job");
|
||||
|
||||
// Skip to container selection (offline mode for faster creation)
|
||||
await panel.getByRole("button", { name: "Next" }).click();
|
||||
|
||||
// Select source containers quickly
|
||||
const sourceDatabaseDropdown = panel.getByTestId("source-databaseDropdown");
|
||||
const dropdownItemsWrapper = frame.locator("div.ms-Dropdown-items");
|
||||
|
||||
await sourceDatabaseDropdown.click();
|
||||
await dropdownItemsWrapper.locator("button.ms-Dropdown-item[role='option']").first().click();
|
||||
|
||||
const sourceContainerDropdown = panel.getByTestId("source-containerDropdown");
|
||||
await sourceContainerDropdown.click();
|
||||
await dropdownItemsWrapper.locator("button.ms-Dropdown-item[role='option']").first().click();
|
||||
|
||||
// Select target containers
|
||||
const targetDatabaseDropdown = panel.getByTestId("target-databaseDropdown");
|
||||
await targetDatabaseDropdown.click();
|
||||
await dropdownItemsWrapper.locator("button.ms-Dropdown-item[role='option']").first().click();
|
||||
|
||||
const targetContainerDropdown = panel.getByTestId("target-containerDropdown");
|
||||
await targetContainerDropdown.click();
|
||||
await dropdownItemsWrapper.locator("button.ms-Dropdown-item[role='option']").nth(1).click();
|
||||
|
||||
await panel.getByRole("button", { name: "Next" }).click();
|
||||
|
||||
// Set job name and create
|
||||
const jobNameInput = panel.getByTestId("job-name-textfield");
|
||||
await jobNameInput.fill(cancelJobName);
|
||||
|
||||
const copyButton = panel.getByRole("button", { name: "Copy", exact: true });
|
||||
|
||||
// Create job and immediately start polling for it
|
||||
await copyButton.click();
|
||||
|
||||
// Wait for panel to close and job list to refresh
|
||||
await expect(panel).not.toBeVisible({ timeout: 10000 });
|
||||
|
||||
const jobsListContainer = wrapper.locator(".CopyJobListContainer .ms-DetailsList-contentWrapper .ms-List-page");
|
||||
await jobsListContainer.waitFor({ state: "visible" });
|
||||
|
||||
// Rapid polling to catch the job in running state
|
||||
let attempts = 0;
|
||||
const maxAttempts = 50; // Try for ~5 seconds
|
||||
let jobCancelled = false;
|
||||
|
||||
while (attempts < maxAttempts && !jobCancelled) {
|
||||
try {
|
||||
// Look for the job row
|
||||
const jobRow = jobsListContainer.locator(".ms-DetailsRow", { hasText: cancelJobName });
|
||||
|
||||
if (await jobRow.isVisible({ timeout: 100 })) {
|
||||
const statusCell = jobRow.locator("[data-automationid='DetailsRowCell'][data-automation-key='CopyJobStatus']");
|
||||
const statusText = await statusCell.textContent({ timeout: 100 });
|
||||
|
||||
// If job is still running/queued, try to cancel it
|
||||
if (statusText && /running|queued|pending/i.test(statusText)) {
|
||||
const actionMenuButton = wrapper.getByTestId(`CopyJobActionMenu/Button:${cancelJobName}`);
|
||||
await actionMenuButton.click({ timeout: 1000 });
|
||||
|
||||
const cancelAction = frame.locator(".ms-ContextualMenu-list button:has-text('Cancel')");
|
||||
if (await cancelAction.isVisible({ timeout: 1000 })) {
|
||||
await cancelAction.click();
|
||||
|
||||
// Verify cancellation
|
||||
await expect(statusCell).toContainText(/cancelled|canceled|failed/i, { timeout: 5000 });
|
||||
jobCancelled = true;
|
||||
break;
|
||||
}
|
||||
} else if (statusText && /completed|succeeded|finished/i.test(statusText)) {
|
||||
// Job completed too fast, skip the test
|
||||
// console.log(`Job ${cancelJobName} completed too quickly to test cancellation`);
|
||||
test.skip(true, "Job completed too quickly for cancellation test");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh the job list
|
||||
const refreshButton = wrapper.getByTestId("CommandBar/Button:Refresh");
|
||||
if (await refreshButton.isVisible({ timeout: 100 })) {
|
||||
await refreshButton.click();
|
||||
await page.waitForTimeout(100); // Small delay between attempts
|
||||
}
|
||||
} catch (error) {
|
||||
// Continue trying if there's any error
|
||||
}
|
||||
|
||||
attempts++;
|
||||
}
|
||||
|
||||
if (!jobCancelled) {
|
||||
// If we couldn't cancel in time, at least verify the job was created
|
||||
const jobRow = jobsListContainer.locator(".ms-DetailsRow", { hasText: cancelJobName });
|
||||
await expect(jobRow).toBeVisible({ timeout: 5000 });
|
||||
test.skip(true, "Could not catch job in running state for cancellation test");
|
||||
}
|
||||
});
|
||||
|
||||
test.afterAll("Container Copy - After All", async () => {
|
||||
await page.unroute(/.*/, (route) => route.continue());
|
||||
await page.close();
|
||||
});
|
||||
@@ -11,8 +11,12 @@ const accountName = urlSearchParams.get("accountName") || "portal-sql-runner-wes
|
||||
const selfServeType = urlSearchParams.get("selfServeType") || "example";
|
||||
const iframeSrc = urlSearchParams.get("iframeSrc") || "explorer.html?platform=Portal&disablePortalInitCache";
|
||||
const authToken = urlSearchParams.get("token");
|
||||
const enablecontainercopy = urlSearchParams.get("enablecontainercopy");
|
||||
|
||||
const nosqlRbacToken = urlSearchParams.get("nosqlRbacToken") || process.env.NOSQL_TESTACCOUNT_TOKEN || "";
|
||||
const nosqlRbacToken =
|
||||
urlSearchParams.get("nosqlRbacToken") ||
|
||||
(enablecontainercopy ? process.env.NOSQL_CONTAINERCOPY_TESTACCOUNT_TOKEN : process.env.NOSQL_TESTACCOUNT_TOKEN) ||
|
||||
"";
|
||||
const nosqlReadOnlyRbacToken =
|
||||
urlSearchParams.get("nosqlReadOnlyRbacToken") || process.env.NOSQL_READONLY_TESTACCOUNT_TOKEN || "";
|
||||
const tableRbacToken = urlSearchParams.get("tableRbacToken") || process.env.TABLE_TESTACCOUNT_TOKEN || "";
|
||||
@@ -83,6 +87,7 @@ const initTestExplorer = async (): Promise<void> => {
|
||||
authorizationToken: `Bearer ${authToken}`,
|
||||
aadToken: rbacToken,
|
||||
features: {},
|
||||
containerCopyEnabled: enablecontainercopy === "true",
|
||||
hasWriteAccess: true,
|
||||
csmEndpoint: "https://management.azure.com",
|
||||
dnsSuffix: "documents.azure.com",
|
||||
|
||||
Reference in New Issue
Block a user