Compare commits

...

1 Commits

Author SHA1 Message Date
Bikram Choudhury
79b55ffd54 Initial MCP setup code 2026-01-15 17:24:46 +05:30
23 changed files with 1591 additions and 22 deletions

View File

@@ -1 +1,20 @@
DATA_EXPLORER_ENDPOINT=https://localhost:1234/hostedExplorer.html
DATA_EXPLORER_ENDPOINT=https://localhost:1234/hostedExplorer.html
# Azure Test Environment Variables
# These are automatically set by the global-setup.ts script
# Azure resource group containing the test resources
DE_TEST_RESOURCE_GROUP=bchoudhury-e2e-testing
# Azure subscription ID for testing
DE_TEST_SUBSCRIPTION_ID=074d02eb-4d74-486a-b299-b262264d1536
# Prefix used for test resource names
DE_TEST_ACCOUNT_PREFIX=bchoudhury-e2e-
# Access token for NoSQL container copy operations
# This is generated automatically by the setup script
NOSQL_CONTAINERCOPY_TESTACCOUNT_TOKEN=
# Optional: Set to true to enable debug logging during setup
DEBUG=false

View File

@@ -0,0 +1,71 @@
---
name: playwright-test-generator
description: 'Expert agent for generating robust Playwright tests for Cosmos DB. Supports DataExplorer and CopyJob flows. Required Inputs: <test-suite>, <test-name>, <test-file>, <seed-file>, <body>'
tools:
- azure-mcp/search
- playwright/browser_click
- playwright/browser_drag
- playwright/browser_evaluate
- playwright/browser_file_upload
- playwright/browser_handle_dialog
- playwright/browser_hover
- playwright/browser_navigate
- playwright/browser_press_key
- playwright/browser_select_option
- playwright/browser_snapshot
- playwright/browser_type
- playwright/browser_wait_for
- filesystem/read_file
- filesystem/write_file
- filesystem/list_directory
- filesystem/create_directory
model: Claude Sonnet 4
---
You are an expert Playwright Test Generator from given planner specs.
Your specialty is creating tests that match the project's high standards for frame navigation and FluentUI interaction. Your expertise includes functional testing using Playwright
# Pre-Generation Analysis
Before generating code, you MUST:
1. **Analyze Existing Specs:** Examine the provided `<seed-file>` and any referenced test files in the project to learn the specific coding standards, naming conventions, and import structures used.
2. **Identify the Flow Type:** Determine if the scenario is a **DataExplorer** flow or a **CopyJob** flow.
- **DataExplorer Flows:** Use `DataExplorer.open()` in the `beforeEach` or `beforeAll` setup logic.
- **CopyJob Flows:** Use `ContainerCopy.open()` in the `beforeEach` or `beforeAll` setup logic.
# Core Project Standards
- **Authentication:** Assume pre-flight authentication (az login/globalSetup) is complete. Do not generate login steps. The `global-setup` has been added into playwright.config.ts
- **Library Usage:** Prioritize common helper methods from `fx.ts`.
- **Page Hierarchy:**
- `frame`: Parent blade locator (used for overlays, dropdown lists, global portal buttons).
- `wrapper`: Child locator (used for the specific view content/form inputs).
- **Selector Strategy:** Use FluentUI-friendly locators: `getByRole` and `getByLabel`. Also prioritize `getByTestId` where available - check for `testIdAttribute` in playwright.config.ts as "data-test" attributes are configured as "data-testid".
# Step-by-Step Generation Workflow
1. **Plan Parsing:** Extract steps and verification logic from the planner spec.
2. **Setup:** Run `generator_setup_page` using the `<seed-file>`.
3. **Observation:** Use Playwright tools to execute steps. Pay close attention to which elements require the `frame` context vs. the `wrapper` context.
4. **Source Code Generation:**
- **File Naming:** Use fs-friendly names based on the scenario.
- **Structure:** Encapsulate in a `test.describe` matching the test plan group.
- **Code Style:** Match the patterns found in the analyzed existing files exactly.
- **Cleanup:** Ensure the test handles data cleanup to remain idempotent.
# Example Logic (Flow Detection)
```ts
// If CopyJob flow:
test.beforeEach(async ({ page }) => {
await ContainerCopy.open // Standardized entry point
});
(or)
test.beforeAll(async ({ page }) => {
await ContainerCopy.open // Standardized entry point
});
// If DataExplorer flow:
test.beforeEach(async ({ page }) => {
await DataExplorer.open // Standardized entry point
});
(or)
test.beforeAll(async ({ page }) => {
await DataExplorer.open // Standardized entry point
});

View File

@@ -0,0 +1,61 @@
---
name: playwright-test-healer
description: Use this agent when you need to debug and fix failing Playwright tests
tools:
- search
- edit
- playwright-test/browser_console_messages
- playwright-test/browser_evaluate
- playwright-test/browser_generate_locator
- playwright-test/browser_network_requests
- playwright-test/browser_snapshot
- playwright-test/test_debug
- playwright-test/test_list
- playwright-test/test_run
model: Claude Sonnet 4
mcp-servers:
playwright-test:
type: stdio
command: npx
args:
- playwright
- run-test-mcp-server
tools:
- "*"
---
You are the Playwright Test Healer, an expert test automation engineer specializing in debugging and resolving Playwright test failures. Your mission is to systematically identify, diagnose, and fix broken Playwright tests using a methodical approach.
Your workflow:
1. **Initial Execution**: Run all tests using `test_run` tool to identify failing tests
2. **Debug failed tests**: For each failing test run `test_debug`.
3. **Error Investigation**: When the test pauses on errors, use available Playwright MCP tools to:
- Examine the error details
- Capture page snapshot to understand the context
- Analyze selectors, timing issues, or assertion failures
4. **Root Cause Analysis**: Determine the underlying cause of the failure by examining:
- Element selectors that may have changed
- Timing and synchronization issues
- Data dependencies or test environment problems
- Application changes that broke test assumptions
5. **Code Remediation**: Edit the test code to address identified issues, focusing on:
- Updating selectors to match current application state
- Fixing assertions and expected values
- Improving test reliability and maintainability
- For inherently dynamic data, utilize regular expressions to produce resilient locators
6. **Verification**: Restart the test after each fix to validate the changes
7. **Iteration**: Repeat the investigation and fixing process until the test passes cleanly
Key principles:
- Be systematic and thorough in your debugging approach
- Document your findings and reasoning for each fix
- Prefer robust, maintainable solutions over quick hacks
- Use Playwright best practices for reliable test automation
- If multiple errors exist, fix them one at a time and retest
- Provide clear explanations of what was broken and how you fixed it
- You will continue this process until the test runs successfully without any failures or errors.
- If the error persists and you have high level of confidence that the test is correct, mark this test as test.fixme()
so that it is skipped during the execution. Add a comment before the failing step explaining what is happening instead
of the expected behavior.
- Do not ask user questions, you are not interactive tool, do the most reasonable thing possible to pass the test.
- Never wait for networkidle or use other discouraged or deprecated apis

View File

@@ -0,0 +1,78 @@
---
name: playwright-test-planner
description: Use this agent when you need to create comprehensive test plan for a web application or website
tools:
- search
- playwright-test/browser_click
- playwright-test/browser_close
- playwright-test/browser_console_messages
- playwright-test/browser_drag
- playwright-test/browser_evaluate
- playwright-test/browser_file_upload
- playwright-test/browser_handle_dialog
- playwright-test/browser_hover
- playwright-test/browser_navigate
- playwright-test/browser_navigate_back
- playwright-test/browser_network_requests
- playwright-test/browser_press_key
- playwright-test/browser_select_option
- playwright-test/browser_snapshot
- playwright-test/browser_take_screenshot
- playwright-test/browser_type
- playwright-test/browser_wait_for
- playwright-test/planner_setup_page
- playwright-test/planner_save_plan
model: Claude Sonnet 4
mcp-servers:
playwright-test:
type: stdio
command: npx
args:
- playwright
- run-test-mcp-server
tools:
- "*"
---
You are an expert web test planner with extensive experience in quality assurance, user experience testing, and test scenario design. Your expertise includes functional testing, edge case identification, and comprehensive test coverage planning.
You will:
1. **Navigate and Explore**
- Invoke the `planner_setup_page` tool once to set up page before using any other tools
- Explore the browser snapshot
- Do not take screenshots unless absolutely necessary
- Use `browser_*` tools to navigate and discover interface
- Thoroughly explore the interface, identifying all interactive elements, forms, navigation paths, and functionality
2. **Analyze User Flows**
- Map out the primary user journeys and identify critical paths through the application
- Consider different user types and their typical behaviors
3. **Design Comprehensive Scenarios**
Create detailed test scenarios that cover:
- Happy path scenarios (normal user behavior)
- Edge cases and boundary conditions
- Error handling and validation
4. **Structure Test Plans**
Each scenario must include:
- Clear, descriptive title
- Detailed step-by-step instructions
- Expected outcomes where appropriate
- Assumptions about starting state (always assume blank/fresh state)
- Success criteria and failure conditions
5. **Create Documentation**
Submit your test plan using `planner_save_plan` tool.
**Quality Standards**:
- Write steps that are specific enough for any tester to follow
- Include negative testing scenarios
- Ensure scenarios are independent and can be run in any order
**Output Format**: Always save the complete test plan as a markdown file with clear headings, numbered steps, and
professional formatting suitable for sharing with development and QA teams.

View File

@@ -0,0 +1,34 @@
name: "Copilot Setup Steps"
on:
workflow_dispatch:
push:
paths:
- .github/workflows/copilot-setup-steps.yml
pull_request:
paths:
- .github/workflows/copilot-setup-steps.yml
jobs:
copilot-setup-steps:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: lts/*
- name: Install dependencies
run: npm ci
- name: Install Playwright Browsers
run: npx playwright install --with-deps
# Customize this step as needed
- name: Build application
run: npx run build

1
.gitignore vendored
View File

@@ -21,3 +21,4 @@ GettingStarted-ignore*.ipynb
/playwright-report/
/blob-report/
/playwright/.cache/
/playwright/.auth/

23
.vscode/mcp.json vendored Normal file
View File

@@ -0,0 +1,23 @@
{
"servers": {
"playwright": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@playwright/mcp@latest"],
"env": {
"ALLOWED_DIRECTORIES": "C:/Users/bchoudhury/MyProjects/cosmos-explorer"
}
},
"filesystem": {
"type": "stdio",
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-filesystem@latest",
"C:/Users/bchoudhury/MyProjects/cosmos-explorer/test",
"C:/Users/bchoudhury/MyProjects/cosmos-explorer/src"
]
}
},
"inputs": []
}

27
package-lock.json generated
View File

@@ -125,7 +125,7 @@
"@babel/preset-env": "7.24.7",
"@babel/preset-react": "7.24.7",
"@babel/preset-typescript": "7.24.7",
"@playwright/test": "1.49.1",
"@playwright/test": "1.57.0",
"@testing-library/react": "11.2.3",
"@types/applicationinsights-js": "1.0.7",
"@types/codemirror": "0.0.56",
@@ -10321,12 +10321,13 @@
}
},
"node_modules/@playwright/test": {
"version": "1.49.1",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.49.1.tgz",
"integrity": "sha512-Ky+BVzPz8pL6PQxHqNRW1k3mIyv933LML7HktS8uik0bUXNCdPhoS/kLihiO1tMf/egaJb4IutXd7UywvXEW+g==",
"version": "1.57.0",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.57.0.tgz",
"integrity": "sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright": "1.49.1"
"playwright": "1.57.0"
},
"bin": {
"playwright": "cli.js"
@@ -30903,12 +30904,13 @@
}
},
"node_modules/playwright": {
"version": "1.49.1",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.49.1.tgz",
"integrity": "sha512-VYL8zLoNTBxVOrJBbDuRgDWa3i+mfQgDTrL8Ah9QXZ7ax4Dsj0MSq5bYgytRnDVVe+njoKnfsYkH3HzqVj5UZA==",
"version": "1.57.0",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz",
"integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.49.1"
"playwright-core": "1.57.0"
},
"bin": {
"playwright": "cli.js"
@@ -30921,10 +30923,11 @@
}
},
"node_modules/playwright-core": {
"version": "1.49.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.49.1.tgz",
"integrity": "sha512-BzmpVcs4kE2CH15rWfzpjzVGhWERJfmnXmniSyKeRZUs9Ws65m+RGIi7mjJK/euCegfn3i7jvqWeWyHe9y3Vgg==",
"version": "1.57.0",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.57.0.tgz",
"integrity": "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"playwright-core": "cli.js"
},

View File

@@ -111,8 +111,8 @@
"tinykeys": "2.1.0",
"underscore": "1.12.1",
"utility-types": "3.10.0",
"web-vitals": "4.2.4",
"uuid": "9.0.0",
"web-vitals": "4.2.4",
"zustand": "3.5.0"
},
"devDependencies": {
@@ -120,7 +120,7 @@
"@babel/preset-env": "7.24.7",
"@babel/preset-react": "7.24.7",
"@babel/preset-typescript": "7.24.7",
"@playwright/test": "1.49.1",
"@playwright/test": "1.57.0",
"@testing-library/react": "11.2.3",
"@types/applicationinsights-js": "1.0.7",
"@types/codemirror": "0.0.56",
@@ -213,7 +213,11 @@
"copyToConsumers": "node copyToConsumers",
"test": "rimraf coverage && jest",
"test:debug": "jest --runInBand",
"test:e2e": "jest -c ./jest.config.playwright.js --detectOpenHandles",
"test:e2e": "npx playwright test",
"test:e2e:setup:verify": "npx playwright test test/setup-verification.spec.ts",
"test:e2e:setup": "node setup-tests.js",
"test:e2e:headed": "npx playwright test --headed",
"test:e2e:ui": "npx playwright test --ui",
"test:file": "jest --coverage=false",
"watch": "npm run start",
"wait-for-server": "wait-on -t 240000 -i 5000 -v https-get://0.0.0.0:1234/",

View File

@@ -6,15 +6,17 @@ export default defineConfig({
testDir: "test",
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 3 : 0,
retries: process.env.CI ? 3 : 2,
workers: process.env.CI ? 1 : undefined,
reporter: process.env.CI ? "blob" : "html",
timeout: 10 * 60 * 1000,
globalSetup: require.resolve('./test/global-setup.ts'),
use: {
trace: "retain-on-failure",
video: "retain-on-failure",
screenshot: "on",
testIdAttribute: "data-test",
// storageState: './test/../playwright/.auth/user.json',
contextOptions: {
ignoreHTTPSErrors: true,
},

48
setup-tests copy.js Normal file
View File

@@ -0,0 +1,48 @@
#!/usr/bin/env node
/**
* Manual setup script to run the pre-flight Azure authentication and configuration
* This can be used independently of Playwright for setting up the testing environment
*/
const { exec } = require('child_process');
const { promisify } = require('util');
const path = require('path');
const execAsync = promisify(exec);
async function runManualSetup() {
console.log('🚀 Starting manual Azure Cosmos DB Explorer test setup...');
try {
// Import the global setup function and run it
const globalSetup = require('./test/global-setup.js');
// Create a minimal config object that matches what Playwright would pass
const mockConfig = {
configFile: path.join(__dirname, 'playwright.config.ts'),
rootDir: __dirname,
testDir: path.join(__dirname, 'test'),
projects: []
};
await globalSetup(mockConfig);
console.log('✅ Manual setup completed successfully!');
console.log('\nYou can now run your Playwright tests with:');
console.log(' npm run test:e2e');
console.log(' or');
console.log(' npx playwright test');
} catch (error) {
console.error('❌ Manual setup failed:', error.message);
process.exit(1);
}
}
// Only run if this script is executed directly
if (require.main === module) {
runManualSetup();
}
module.exports = { runManualSetup };

48
setup-tests.ts Normal file
View File

@@ -0,0 +1,48 @@
#!/usr/bin/env node
/**
* Manual setup script to run the pre-flight Azure authentication and configuration
* This can be used independently of Playwright for setting up the testing environment
*/
async function runManualSetup(): Promise<void> {
console.log('🚀 Starting manual Azure Cosmos DB Explorer test setup...');
try {
// Dynamically import the Playwright global setup
const globalSetupModule = await import('./test/global-setup');
const globalSetup = globalSetupModule.default;
if (typeof globalSetup !== 'function') {
throw new Error('global-setup.ts does not export a default function');
}
// Minimal mock config similar to what Playwright provides
/* const mockConfig: FullConfig = {
configFile: path.join(__dirname, 'playwright.config.ts'),
rootDir: __dirname,
testDir: path.join(__dirname, 'test'),
projects: []
}; */
await globalSetup();
console.log('✅ Manual setup completed successfully!');
console.log('\nYou can now run your Playwright tests with:');
console.log(' npm run test:e2e');
console.log(' or');
console.log(' npx playwright test');
} catch (error: any) {
console.error('❌ Manual setup failed:', error.message);
process.exit(1);
}
}
// Only run if executed directly (not imported)
if (require.main === module) {
void runManualSetup();
}
export { runManualSetup };

163
test/E2E-SETUP.md Normal file
View File

@@ -0,0 +1,163 @@
# E2E Testing Setup for Cosmos DB Explorer
This document describes the pre-flight setup process for running end-to-end tests in the Cosmos DB Explorer project.
## Overview
The testing setup includes:
1. Azure CLI authentication
2. Subscription selection
3. Test account configuration
4. Token generation for NoSQL container operations
5. Browser storage state setup for Playwright
## Prerequisites
Before running tests, ensure you have:
- [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli) installed and updated
- PowerShell execution policy set to allow script execution
- Access to the Azure subscription: `074d02eb-4d74-486a-b299-b262264d1536`
- Access to the resource group: `bchoudhury-e2e-testing`
- Required Azure resources with prefix: `bchoudhury-e2e-`
## Automatic Setup (Recommended)
The setup runs automatically when you start Playwright tests:
```bash
npm run test:e2e
```
or
```bash
npx playwright test
```
The global setup script (`test/global-setup.ts`) will:
1. Check if you're logged in to Azure CLI
2. If not logged in, prompt for authentication
3. Set the correct subscription
4. Run the PowerShell configuration script
5. Generate required access tokens
6. Set up browser authentication state
## Manual Setup
If you need to run the setup independently:
```bash
npm run test:e2e:setup
```
or directly:
```bash
node setup-tests.js
```
## Manual Step-by-Step Setup
If you prefer to run each step manually:
### 1. Azure CLI Login
```bash
az login --scope https://management.core.windows.net//.default
```
### 2. Set Subscription
```bash
az account set --subscription "074d02eb-4d74-486a-b299-b262264d1536"
```
### 3. Configure Test Accounts
```powershell
.\test\scripts\set-test-accounts.ps1 -ResourceGroup "bchoudhury-e2e-testing" -Subscription "074d02eb-4d74-486a-b299-b262264d1536" -ResourcePrefix "bchoudhury-e2e-"
```
### 4. Set NoSQL Container Token
```bash
$ENV:NOSQL_CONTAINERCOPY_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://bchoudhury-e2e-sqlcontainercopyonly.documents.azure.com/.default" -o tsv --query accessToken)
```
## Environment Variables
The setup process configures these environment variables:
- `DE_TEST_RESOURCE_GROUP`: The Azure resource group for testing
- `DE_TEST_SUBSCRIPTION_ID`: The Azure subscription ID
- `DE_TEST_ACCOUNT_PREFIX`: The prefix used for test resources
- `NOSQL_CONTAINERCOPY_TESTACCOUNT_TOKEN`: Access token for NoSQL operations
## Storage State
Browser authentication state is saved to:
```
playwright/.auth/user.json
```
This file is automatically generated and excluded from version control.
## Troubleshooting
### Common Issues
1. **Azure CLI not found**
- Install Azure CLI: https://docs.microsoft.com/en-us/cli/azure/install-azure-cli
2. **PowerShell execution policy error**
```powershell
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
```
3. **Authentication expired**
```bash
az logout
az login --scope https://management.core.windows.net//.default
```
4. **Resource not found**
- Verify you have access to the subscription and resource group
- Check that resources exist with the expected prefix
### Debug Mode
For verbose output during setup, you can run:
```bash
DEBUG=1 npm run test:e2e:setup
```
## File Structure
```
test/
├── global-setup.ts # Main global setup script
├── scripts/
│ ├── set-test-accounts.ps1 # PowerShell test account setup
│ ├── check-test-accounts.ps1
│ └── clean-test-accounts.ps1
└── ...
playwright/
└── .auth/
└── user.json # Browser storage state (generated)
setup-tests.js # Manual setup runner
playwright.config.ts # Playwright config with global setup
```
## Configuration
The setup is configured in:
- `playwright.config.ts`: Playwright configuration with global setup
- `test/global-setup.ts`: Main setup logic
- `setup-tests.js`: Manual setup runner
## Notes
- The setup only needs to run once per session
- Browser storage state persists between test runs
- Azure tokens are refreshed automatically by the Azure CLI
- The setup is idempotent - safe to run multiple times

View File

@@ -701,6 +701,7 @@ export class ContainerCopy {
static async open(page: Page, testAccount: TestAccount, iframeSrc?: string): Promise<ContainerCopy> {
const url = await getTestExplorerUrl(testAccount, { iframeSrc, enablecontainercopy: true });
// console.log(`Navigating to URL: ${url}`);
await page.goto(url);
return ContainerCopy.waitForContainerCopy(page);
}

145
test/global-setup copy.js Normal file
View File

@@ -0,0 +1,145 @@
const { chromium } = require('@playwright/test');
const { exec } = require('child_process');
const fs = require('fs').promises;
const path = require('path');
const { promisify } = require('util');
const execAsync = promisify(exec);
async function globalSetup(config) {
console.log('🚀 Starting global setup for Azure Cosmos DB Explorer tests...');
try {
// Step 1: Check if user is already logged in to Azure CLI
console.log('📋 Checking Azure CLI login status...');
try {
const { stdout: accountInfo } = await execAsync('az account show --output json');
const account = JSON.parse(accountInfo);
console.log(`✅ Already logged in as: ${account.user?.name} in subscription: ${account.name}`);
} catch (error) {
console.log('🔐 Not logged in to Azure CLI, initiating login...');
// Step 2: Login to Azure with specific scope
console.log('🔑 Logging in to Azure CLI...');
console.log('Please complete the authentication in your browser...');
await execAsync('az login --scope https://management.core.windows.net//.default');
console.log('✅ Azure CLI login completed');
}
// Step 3: Set the subscription
const targetSubscription = '074d02eb-4d74-486a-b299-b262264d1536';
console.log(`🔄 Setting subscription to: ${targetSubscription}`);
await execAsync(`az account set --subscription "${targetSubscription}"`);
console.log('✅ Subscription set successfully');
// Step 4: Run the PowerShell test account setup script
console.log('⚙️ Running test account setup script...');
const scriptPath = path.join(__dirname, 'scripts', 'set-test-accounts.ps1');
const resourceGroup = 'bchoudhury-e2e-testing';
const resourcePrefix = 'bchoudhury-e2e-';
// Try PowerShell 7 first, fallback to Windows PowerShell if not available
let psCommand = `pwsh.exe -NoProfile -ExecutionPolicy Bypass -File "${scriptPath}" -ResourceGroup "${resourceGroup}" -Subscription "${targetSubscription}" -ResourcePrefix "${resourcePrefix}"`;
try {
const { stdout, stderr } = await execAsync(psCommand);
if (stdout) console.log('PowerShell Output:', stdout);
if (stderr) console.log('PowerShell Warnings:', stderr);
console.log('✅ Test account setup completed');
} catch (psError) {
console.log('🔄 PowerShell 7 not available, trying Windows PowerShell...');
// Fallback to Windows PowerShell with NoProfile to avoid module loading issues
const fallbackCommand = `powershell.exe -NoProfile -ExecutionPolicy Bypass -File "${scriptPath}" -ResourceGroup "${resourceGroup}" -Subscription "${targetSubscription}" -ResourcePrefix "${resourcePrefix}"`;
try {
const { stdout: fbStdout, stderr: fbStderr } = await execAsync(fallbackCommand);
if (fbStdout) console.log('PowerShell Output:', fbStdout);
if (fbStderr) console.log('PowerShell Warnings:', fbStderr);
console.log('✅ Test account setup completed');
} catch (fallbackError) {
console.error('❌ PowerShell script execution failed:', fallbackError.message);
console.warn('⚠️ The PowerShell script failed. This might be due to:');
console.warn(' 1. PowerShell version compatibility (script requires PowerShell 7+ for ?? operator)');
console.warn(' 2. Missing Azure PowerShell modules');
console.warn(' 3. Insufficient permissions or missing resources');
// Don't throw here as the script might have partial success
}
}
// Step 5: Set the NoSQL container copy test account token
console.log('🎟️ Setting up NoSQL container copy test account token...');
try {
const { stdout: tokenOutput } = await execAsync(
'az account get-access-token --scope "https://bchoudhury-e2e-sqlcontainercopyonly.documents.azure.com/.default" -o tsv --query accessToken'
);
const token = tokenOutput.trim();
if (token) {
process.env.NOSQL_CONTAINERCOPY_TESTACCOUNT_TOKEN = token;
console.log('✅ NoSQL container copy token set successfully');
} else {
console.warn('⚠️ Failed to retrieve NoSQL container copy token');
}
} catch (tokenError) {
console.error('❌ Failed to set NoSQL token:', tokenError.message);
// Continue without throwing as this might not be critical for all tests
}
// Step 6: Create browser context and save storage state for authentication
console.log('🌐 Setting up browser authentication state...');
const browser = await chromium.launch();
const context = await browser.newContext({
ignoreHTTPSErrors: true,
});
const page = await context.newPage();
// Navigate to a login page or perform any authentication steps if needed
// For now, we'll just create an empty storage state that can be populated later
// Save the storage state
const storageStatePath = path.join(__dirname, '../playwright/.auth/user.json');
await fs.mkdir(path.dirname(storageStatePath), { recursive: true });
await context.storageState({ path: storageStatePath });
await browser.close();
console.log(`✅ Browser storage state saved to: ${storageStatePath}`);
// Step 7: Set environment variables that tests might need
console.log('📝 Setting up environment variables for tests...');
// Verify required environment variables are set
const requiredEnvVars = [
'DE_TEST_RESOURCE_GROUP',
'DE_TEST_SUBSCRIPTION_ID',
'DE_TEST_ACCOUNT_PREFIX'
];
const missingVars = requiredEnvVars.filter(varName => !process.env[varName]);
if (missingVars.length > 0) {
console.warn(`⚠️ Missing environment variables: ${missingVars.join(', ')}`);
console.warn('These should be set by the PowerShell script. Tests may fail.');
} else {
console.log('✅ All required environment variables are set');
}
console.log('🎉 Global setup completed successfully!');
console.log('\n📊 Setup Summary:');
console.log(` - Subscription: ${targetSubscription}`);
console.log(` - Resource Group: ${resourceGroup}`);
console.log(` - Resource Prefix: ${resourcePrefix}`);
console.log(` - Storage State: ${storageStatePath}`);
} catch (error) {
console.error('❌ Global setup failed:', error.message);
console.error('Stack trace:', error.stack);
throw error;
}
}
module.exports = globalSetup;

135
test/global-setup.ts Normal file
View File

@@ -0,0 +1,135 @@
import { type FullConfig } from '@playwright/test';
import { exec } from 'child_process';
import path from 'path';
import { promisify } from 'util';
const execAsync = promisify(exec);
export default async function globalSetup(
_config?: FullConfig
): Promise<void> {
console.log('🚀 Starting global setup for Azure Cosmos DB Explorer tests...');
try {
// ------------------------------------------------------------------
// Step 1: Azure CLI login
// ------------------------------------------------------------------
console.log('📋 Checking Azure CLI login status...');
try {
const { stdout } = await execAsync('az account show --output json');
const account = JSON.parse(stdout);
console.log(
`✅ Already logged in as: ${account.user?.name} in subscription: ${account.name}`
);
} catch {
console.log('🔐 Not logged in to Azure CLI, initiating login...');
await execAsync(
'az login --scope https://management.core.windows.net//.default'
);
console.log('✅ Azure CLI login completed');
}
// ------------------------------------------------------------------
// Step 2: Set subscription
// ------------------------------------------------------------------
const targetSubscription = '074d02eb-4d74-486a-b299-b262264d1536';
console.log(`🔄 Setting subscription to: ${targetSubscription}`);
await execAsync(
`az account set --subscription "${targetSubscription}"`
);
// ------------------------------------------------------------------
// Step 3: Run PowerShell test account setup (DOT-SOURCED)
// ------------------------------------------------------------------
console.log('⚙️ Running test account setup script...');
const scriptPath = path.join(
__dirname,
'scripts',
'set-test-accounts.ps1'
);
const resourceGroup = 'bchoudhury-e2e-testing';
const resourcePrefix = 'bchoudhury-e2e-';
// IMPORTANT:
// - Dot-source the PS1
// - Print env vars BEFORE PowerShell exits
const psScript = `
. "${scriptPath}" `
+ `-ResourceGroup "${resourceGroup}" `
+ `-Subscription "${targetSubscription}" `
+ `-ResourcePrefix "${resourcePrefix}"
Get-ChildItem Env:DE_TEST_* | ForEach-Object {
Write-Output "$($_.Name)=$($_.Value)"
}
`;
// PowerShell requires UTF-16LE encoding for -EncodedCommand
const encodedCommand = Buffer.from(psScript, 'utf16le').toString('base64');
const { stdout: psStdout, stderr: psStderr } = await execAsync(
`pwsh -NoProfile -ExecutionPolicy Bypass -EncodedCommand ${encodedCommand}`
);
if (psStderr) {
console.log('PowerShell warnings:', psStderr);
}
// ------------------------------------------------------------------
// Step 3a: Import env vars from PowerShell
// ------------------------------------------------------------------
psStdout
.split(/\r?\n/)
.map(line => line.trim())
.filter(line => line.startsWith('DE_TEST_'))
.forEach(line => {
const [key, ...rest] = line.split('=');
process.env[key] = rest.join('=');
console.log(`✅ Imported env var from PS: ${key}`);
});
// ------------------------------------------------------------------
// Step 4: NoSQL access token
// ------------------------------------------------------------------
console.log('🎟️ Setting up NoSQL container copy test account token...');
try {
const { stdout } = await execAsync(
'az account get-access-token --scope "https://bchoudhury-e2e-sqlcontainercopyonly.documents.azure.com/.default" -o tsv --query accessToken'
);
const token = stdout.trim();
if (token) {
process.env.NOSQL_CONTAINERCOPY_TESTACCOUNT_TOKEN = token;
console.log('✅ NoSQL container copy token set successfully');
}
} catch (err: any) {
console.error('❌ Failed to set NoSQL token:', err.message);
}
// Browser authentication not needed - using API tokens directly
// ------------------------------------------------------------------
// Step 6: Validate env vars
// ------------------------------------------------------------------
console.log('📝 Validating environment variables...');
const requiredEnvVars = [
'DE_TEST_RESOURCE_GROUP',
'DE_TEST_SUBSCRIPTION_ID',
'DE_TEST_ACCOUNT_PREFIX',
];
const missing = requiredEnvVars.filter(v => !process.env[v]);
if (missing.length > 0) {
console.warn(`⚠️ Missing environment variables: ${missing.join(', ')}`);
} else {
console.log('✅ All required environment variables are set');
}
console.log('🎉 Global setup completed successfully!');
} catch (error: any) {
console.error('❌ Global setup failed:', error.message);
console.error(error.stack);
throw error;
}
}

View File

@@ -18,7 +18,10 @@ if (-not $Subscription) {
$Subscription = $currentSubscription.Id
}
$AzSubscription = (Get-AzSubscription -SubscriptionId $Subscription -ErrorAction SilentlyContinue | Select-Object -First 1) ?? (Get-AzSubscription -SubscriptionName $Subscription -ErrorAction SilentlyContinue | Select-Object -First 1)
$AzSubscription = (Get-AzSubscription -SubscriptionId $Subscription -ErrorAction SilentlyContinue | Select-Object -First 1)
if (-not $AzSubscription) {
$AzSubscription = (Get-AzSubscription -SubscriptionName $Subscription -ErrorAction SilentlyContinue | Select-Object -First 1)
}
if (-not $AzSubscription) {
throw "The subscription '$Subscription' could not be found."
}

7
test/seed.spec.ts Normal file
View File

@@ -0,0 +1,7 @@
import { test, expect } from '@playwright/test';
test.describe('Test group', () => {
test('seed', async ({ page }) => {
// generate code here.
});
});

View File

@@ -0,0 +1,51 @@
import { expect, test } from '@playwright/test';
test.describe('Azure Setup Verification', () => {
test('should have required environment variables set', async ({ page }) => {
// Verify that the global setup has properly configured the environment
const requiredEnvVars = [
'DE_TEST_RESOURCE_GROUP',
'DE_TEST_SUBSCRIPTION_ID',
'DE_TEST_ACCOUNT_PREFIX'
];
for (const envVar of requiredEnvVars) {
expect(process.env[envVar], `Environment variable ${envVar} should be set by global setup`).toBeDefined();
expect(process.env[envVar], `Environment variable ${envVar} should not be empty`).not.toBe('');
}
console.log('✅ Environment Variables:');
console.log(` DE_TEST_RESOURCE_GROUP: ${process.env.DE_TEST_RESOURCE_GROUP}`);
console.log(` DE_TEST_SUBSCRIPTION_ID: ${process.env.DE_TEST_SUBSCRIPTION_ID}`);
console.log(` DE_TEST_ACCOUNT_PREFIX: ${process.env.DE_TEST_ACCOUNT_PREFIX}`);
if (process.env.NOSQL_CONTAINERCOPY_TESTACCOUNT_TOKEN) {
console.log(` NOSQL_CONTAINERCOPY_TESTACCOUNT_TOKEN: [SET]`);
} else {
console.log(` NOSQL_CONTAINERCOPY_TESTACCOUNT_TOKEN: [NOT SET]`);
}
});
/* test('should be able to navigate to the application', async ({ page }) => {
// This assumes the web server is running on https://127.0.0.1:1234
// as configured in playwright.config.ts
try {
await page.goto('https://127.0.0.1:1234');
// Wait for the page to load
await page.waitForTimeout(2000);
// Check if the page loaded successfully
const title = await page.title();
console.log(`Page title: ${title}`);
// This is a basic check - you might want to check for specific elements
// that indicate the Cosmos DB Explorer has loaded correctly
expect(title).toBeTruthy();
} catch (error) {
console.log('Note: Application server might not be running. Start it with: npm run start');
throw error;
}
}); */
});

363
test/sql/copy-job-create.md Normal file
View File

@@ -0,0 +1,363 @@
# Copy Job Creation Test Plan
## Application Overview
This test plan covers comprehensive testing of the Copy Job Creation flow in the Azure CosmosDB Portal. The feature allows users to create container copy jobs for migrating data between Cosmos DB containers with support for both online and offline migration modes. Testing focuses on UI validation, form submission, error handling, and end-to-end workflow verification.
## Test Scenarios
### 1. Copy Job Creation - Happy Path
**Seed:** `test/sql/copyjob.seed.spec.ts`
#### 1.1. Create Offline Copy Job Successfully
**File:** `tests/copy-job-creation/create-offline-copy-job-success.spec.ts`
**Steps:**
1. Wait for the copy job screen to load
2. Click 'Create Copy Job' button with data-test='CommandBar/Button:Create Copy Job'
3. Verify side panel opens with data-test='Panel:Create copy job'
4. Verify panel header contains 'Create copy job' text
5. Wait for subscription dropdown with data-test='subscription-dropdown' to populate
6. Wait for account dropdown with data-test='account-dropdown' to populate
7. Verify 'Offline mode' radio button is enabled and selected by default
8. Verify offline mode description with data-test='migration-type-description-offline' is visible
9. Click 'Next' button to proceed to container selection
10. Verify SelectSourceAndTargetContainers panel with data-test='Panel:SelectSourceAndTargetContainers' is visible
11. Select first option from source database dropdown with data-test='source-databaseDropdown'
12. Select first option from source container dropdown with data-test='source-containerDropdown'
13. Select first option from target database dropdown with data-test='target-databaseDropdown'
14. Select first option from target container dropdown with data-test='target-containerDropdown'
15. Verify error message appears at top of panel for same source and target selection
16. Select second option from target container dropdown to resolve error
17. Click 'Next' button to proceed to preview
18. Verify preview page displays selected source subscription, account, database and container
19. Enter valid job name in job name input field
20. Click 'Submit' button to create copy job
21. Wait for API response indicating successful job creation
22. Verify job appears in jobs list with correct name
23. Verify side panel is closed after successful submission
**Expected Results:**
- Copy job screen loads successfully
- Create Copy Job button is visible and clickable
- Side panel opens with correct title
- Panel header displays 'Create copy job'
- Subscription dropdown populates with available subscriptions
- Account dropdown populates with available accounts
- Offline mode radio button is enabled and selected
- Offline mode description is visible and contains expected text
- Navigation to container selection screen is successful
- Source and target container selection panel is displayed
- Database and container dropdowns populate with available options
- Validation error is displayed for identical source and target containers
- Error is resolved when different target container is selected
- Navigation to preview screen is successful
- Preview displays accurate selection details
- Job name can be entered without restrictions
- Copy job is successfully created via API
- New job appears in the monitoring list
- Side panel closes indicating completed workflow
#### 1.2. Create Online Copy Job Successfully
**File:** `tests/copy-job-creation/create-online-copy-job-success.spec.ts`
**Steps:**
1. Wait for the copy job screen to load
2. Click 'Create Copy Job' button
3. Verify side panel opens
4. Select 'Online mode' radio button
5. Verify online mode description with data-test='migration-type-description-online' is visible
6. Click 'Next' button
7. Verify permissions assignment panel with data-test='Panel:AssignPermissionsContainer' is displayed
8. Complete permissions assignment process
9. Navigate to container selection screen
10. Select source database and container
11. Select different target database and container
12. Navigate to preview screen
13. Verify all selected details are accurate
14. Enter valid job name
15. Submit copy job creation
16. Verify successful job creation and list update
**Expected Results:**
- Online mode selection triggers permissions workflow
- Permissions assignment panel is displayed correctly
- Online copy job is created successfully
- Job appears in list with online mode indicator
- All workflow steps complete without errors
### 2. Copy Job Creation - Validation & Error Handling
**Seed:** `test/sql/copyjob.seed.spec.ts`
#### 2.1. Validate Required Field Errors
**File:** `tests/copy-job-creation/required-field-validation.spec.ts`
**Steps:**
1. Open Create Copy Job panel
2. Attempt to click 'Next' without selecting subscription
3. Verify validation error for subscription field
4. Select subscription but leave account unselected
5. Attempt to proceed and verify account validation error
6. Select account and proceed to container selection
7. Attempt to proceed without selecting source database
8. Verify source database validation error
9. Select source database but leave container unselected
10. Verify source container validation error
11. Complete source selection but leave target unselected
12. Verify target selection validation errors
13. Complete all selections but proceed to preview with empty job name
14. Verify job name validation error with data-test='Panel:ErrorContainer'
**Expected Results:**
- Subscription selection is enforced
- Account selection is required after subscription selection
- Source database selection is mandatory
- Source container selection is mandatory
- Target database and container selection is mandatory
- Job name is required and properly validated
- All validation messages are clear and actionable
#### 2.2. Validate Job Name Input Restrictions
**File:** `tests/copy-job-creation/job-name-validation.spec.ts`
**Steps:**
1. Navigate through copy job creation to preview screen
2. Test job name with special characters (!@#$%^&*())
3. Verify validation error for invalid characters
4. Test job name with spaces at beginning and end
5. Verify whitespace handling
6. Test extremely long job name (>100 characters)
7. Verify length validation
8. Test job name with only numbers
9. Test job name starting with hyphen or underscore
10. Test valid job name with alphanumeric, hyphens, and underscores
11. Verify successful submission with valid name
**Expected Results:**
- Special characters trigger appropriate validation error
- Whitespace is handled according to business rules
- Length restrictions are enforced
- Numeric-only names are handled correctly
- Valid naming patterns are accepted
- Job creation succeeds with properly formatted name
#### 2.3. Handle Same Source and Target Selection Error
**File:** `tests/copy-job-creation/same-source-target-validation.spec.ts`
**Steps:**
1. Complete subscription and account selection
2. Navigate to container selection screen
3. Select same database for both source and target
4. Select same container for both source and target
5. Attempt to proceed to next screen
6. Verify error message appears with data-test='Panel:ErrorContainer'
7. Verify error message content indicates source and target cannot be identical
8. Change target container to different option
9. Verify error message disappears
10. Verify 'Next' button becomes enabled
11. Successfully proceed to preview screen
**Expected Results:**
- Identical source and target selection triggers validation error
- Error message clearly explains the restriction
- Error is dynamically resolved when selection changes
- Navigation is blocked while validation error persists
- Navigation resumes once valid selections are made
#### 2.4. Handle Network and API Errors
**File:** `tests/copy-job-creation/api-error-handling.spec.ts`
**Steps:**
1. Complete entire copy job creation flow
2. Mock API failure for job creation request
3. Submit copy job with valid data
4. Verify error message appears in panel
5. Verify panel remains open for retry
6. Mock network timeout scenario
7. Verify appropriate timeout error handling
8. Mock authentication error response
9. Verify auth error is handled gracefully
10. Restore normal API behavior
11. Verify successful submission after resolving API issues
**Expected Results:**
- API errors are caught and displayed to user
- Error messages are informative and actionable
- Panel remains accessible for retry attempts
- Different error types are handled appropriately
- Recovery flow works after resolving issues
### 3. Copy Job Creation - Edge Cases & Boundary Testing
**Seed:** `test/sql/copyjob.seed.spec.ts`
#### 3.1. Handle Large Numbers of Containers
**File:** `tests/copy-job-creation/large-container-lists.spec.ts`
**Steps:**
1. Set up test account with 50+ databases and containers
2. Open copy job creation panel
3. Navigate to container selection
4. Test dropdown performance with large lists
5. Verify search/filter functionality in dropdowns
6. Verify scrolling behavior in dropdown menus
7. Select containers from end of large lists
8. Complete job creation with selections from large datasets
**Expected Results:**
- Dropdowns handle large datasets without performance issues
- Search/filter functionality works correctly
- Scrolling in dropdowns is smooth and responsive
- Selections from any position in large lists work correctly
- Job creation completes successfully with large dataset selections
#### 3.2. Test Unicode and International Characters
**File:** `tests/copy-job-creation/unicode-character-handling.spec.ts`
**Steps:**
1. Navigate to job name input in preview screen
2. Test job name with Chinese characters (测试工作)
3. Test job name with Arabic characters (اختبار)
4. Test job name with emoji characters (📁💾)
5. Test job name with accented characters (café résumé)
6. Test mixed unicode and ASCII characters
7. Verify display and storage of unicode job names
8. Submit jobs with various unicode names
9. Verify unicode names appear correctly in jobs list
**Expected Results:**
- Unicode characters are handled correctly in input fields
- Job names with international characters are accepted
- Unicode names display correctly throughout UI
- Jobs with unicode names are created successfully
- Unicode job names appear correctly in monitoring list
#### 3.3. Test Browser Compatibility Features
**File:** `tests/copy-job-creation/browser-compatibility.spec.ts`
**Steps:**
1. Test copy job creation with browser back/forward navigation
2. Verify panel state preservation during navigation
3. Test browser refresh during copy job creation
4. Verify appropriate handling of page refresh
5. Test browser zoom levels (50%, 150%, 200%)
6. Verify UI scaling and usability at different zoom levels
7. Test with browser developer tools open
8. Verify functionality with reduced viewport size
**Expected Results:**
- Browser navigation doesn't break the copy job flow
- Panel state is preserved appropriately
- Page refresh handling is graceful
- UI remains functional at various zoom levels
- Responsive design adapts to different viewport sizes
#### 3.4. Test Concurrent User Scenarios
**File:** `tests/copy-job-creation/concurrent-operations.spec.ts`
**Steps:**
1. Start copy job creation process
2. Simulate another user creating job with same name
3. Attempt to submit copy job with conflicting name
4. Verify appropriate conflict resolution
5. Test creating multiple jobs in rapid succession
6. Verify each job creation is handled independently
7. Test opening multiple copy job creation panels
8. Verify panel management and state isolation
**Expected Results:**
- Name conflicts are detected and handled appropriately
- Multiple simultaneous job creations are managed correctly
- Each copy job creation maintains independent state
- Concurrent operations don't interfere with each other
### 4. Copy Job Creation - UI/UX Validation
**Seed:** `test/sql/copyjob.seed.spec.ts`
#### 4.1. Verify Panel Navigation and Controls
**File:** `tests/copy-job-creation/panel-navigation.spec.ts`
**Steps:**
1. Open copy job creation panel
2. Verify 'Previous' button is disabled on first screen
3. Verify 'Next' button state changes based on validation
4. Navigate forward through all screens
5. Verify 'Previous' button enables navigation backward
6. Verify breadcrumb or progress indication
7. Test 'Cancel' button at each step
8. Verify cancel confirmation dialog if applicable
9. Test panel close button (X) functionality
10. Verify unsaved changes warning if applicable
**Expected Results:**
- Navigation buttons are enabled/disabled appropriately
- Forward navigation respects validation requirements
- Backward navigation preserves user data
- Progress indication is clear and accurate
- Cancel functionality works consistently
- Panel close behavior is user-friendly
- Unsaved changes are handled appropriately
#### 4.2. Verify Dropdown and Form Controls
**File:** `tests/copy-job-creation/form-controls-validation.spec.ts`
**Steps:**
1. Test all dropdown controls for proper data-test attributes
2. Verify dropdown placeholder text is informative
3. Test dropdown option selection and deselection
4. Verify dropdown filtering/search if available
5. Test keyboard navigation in dropdowns (Tab, Enter, Escape)
6. Verify dropdown accessibility attributes (aria-label, role)
7. Test radio button selection and mutual exclusivity
8. Verify radio button labels and descriptions
9. Test input field focus and blur behavior
10. Verify form validation timing (on blur vs on submit)
**Expected Results:**
- All dropdowns have correct data-test attributes for automation
- Placeholder text provides clear guidance
- Option selection works reliably
- Keyboard navigation follows accessibility standards
- ARIA attributes support screen readers
- Radio buttons enforce single selection
- Form validation provides immediate feedback
- Input fields behave predictably
#### 4.3. Verify Loading States and Feedback
**File:** `tests/copy-job-creation/loading-states.spec.ts`
**Steps:**
1. Open copy job creation panel
2. Verify loading indicators while fetching subscriptions
3. Verify loading states for account dropdown population
4. Test loading behavior for database and container lists
5. Verify loading indicator during job submission
6. Test behavior when API calls take longer than expected
7. Verify loading state accessibility (announcements)
8. Test loading state cancellation if supported
9. Verify error states replace loading states appropriately
10. Test loading state recovery after temporary failures
**Expected Results:**
- Loading indicators appear for all asynchronous operations
- Loading states are visually clear and accessible
- Long-running operations provide appropriate feedback
- Loading states can be cancelled when appropriate
- Error handling gracefully replaces loading states
- Recovery from temporary failures is smooth

View File

@@ -0,0 +1,275 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { expect, Frame, Locator, Page, test } from "@playwright/test";
import {
ContainerCopy,
getAccountName,
getDropdownItemByNameOrPosition,
TestAccount,
waitForApiResponse
} from "../fx";
import { createMultipleTestContainers } from "../testData";
let page: Page;
let wrapper: Locator = null!;
let panel: Locator = null!;
let frame: Frame = null!;
let targetAccountName: string = "";
let expectedJobName: string = "";
const VISIBLE_TIMEOUT_MS = 30 * 1000;
test.describe.configure({ mode: "serial" });
test.describe("Copy Job Creation - Happy Path (Scenario 1)", () => {
test.beforeAll("Copy Job Creation - Before All", async ({ browser }) => {
await createMultipleTestContainers({ accountType: TestAccount.SQLContainerCopyOnly, containerCount: 3 });
page = await browser.newPage();
({ wrapper, frame } = await ContainerCopy.open(page, TestAccount.SQLContainerCopyOnly));
expectedJobName = `test_job_${Date.now()}`;
targetAccountName = getAccountName(TestAccount.SQLContainerCopyOnly);
});
test.afterAll("Copy Job Creation - After All", async () => {
await page.unroute(/.*/, (route) => route.continue());
await page.close();
});
test.afterEach("Copy Job Creation - After Each", async () => {
await page.unroute(/.*/, (route) => route.continue());
});
test("1.1. Create Offline Copy Job Successfully", async () => {
// Step 1: Wait for the copy job screen to load
await expect(wrapper.getByTestId("CommandBar/Button:Create Copy Job")).toBeVisible({ timeout: VISIBLE_TIMEOUT_MS });
// Step 2: Click 'Create Copy Job' button
await wrapper.getByTestId("CommandBar/Button:Create Copy Job").click();
// Step 3: Verify side panel opens
panel = frame.getByTestId("Panel:Create copy job");
await expect(panel).toBeVisible({ timeout: VISIBLE_TIMEOUT_MS });
// Step 4: Verify panel header contains 'Create copy job' text
await expect(panel.getByText("Create copy job")).toBeVisible();
// Step 5: Wait for subscription dropdown to populate
const subscriptionDropdown = panel.getByTestId("subscription-dropdown");
await expect(subscriptionDropdown).toBeVisible({ timeout: VISIBLE_TIMEOUT_MS });
await subscriptionDropdown.waitFor({ state: "visible" });
// Step 6: Wait for account dropdown to populate
const accountDropdown = panel.getByTestId("account-dropdown");
await expect(accountDropdown).toBeVisible({ timeout: VISIBLE_TIMEOUT_MS });
await accountDropdown.waitFor({ state: "visible" });
// Step 7: Verify 'Offline mode' radio button is enabled and selected by default
const offlineModeRadio = panel.getByRole("radio", { name: /offline/i });
await expect(offlineModeRadio).toBeVisible();
await expect(offlineModeRadio).toBeEnabled();
await expect(offlineModeRadio).toBeChecked();
// Step 8: Verify offline mode description is visible
const offlineModeDescription = panel.getByTestId("migration-type-description-offline");
await expect(offlineModeDescription).toBeVisible();
// Step 9: Click 'Next' button to proceed to container selection
const nextButton = panel.getByRole("button", { name: /next/i });
await nextButton.click();
// Step 10: Verify SelectSourceAndTargetContainers panel is visible
const containerSelectionPanel = frame.getByTestId("Panel:SelectSourceAndTargetContainers");
await expect(containerSelectionPanel).toBeVisible({ timeout: VISIBLE_TIMEOUT_MS });
// Step 11: Select first option from source database dropdown
const sourceDatabaseDropdown = containerSelectionPanel.getByTestId("source-databaseDropdown");
await expect(sourceDatabaseDropdown).toBeVisible();
await sourceDatabaseDropdown.click();
const sourceDatabaseDropdownItem = await getDropdownItemByNameOrPosition(frame, { position: 0 });
await sourceDatabaseDropdownItem.click();
// Step 12: Select first option from source container dropdown
const sourceContainerDropdown = containerSelectionPanel.getByTestId("source-containerDropdown");
await expect(sourceContainerDropdown).toBeVisible();
await sourceContainerDropdown.click();
const sourceContainerDropdownItem = await getDropdownItemByNameOrPosition(frame, { position: 0 });
await sourceContainerDropdownItem.click();
// Step 13: Select first option from target database dropdown
const targetDatabaseDropdown = containerSelectionPanel.getByTestId("target-databaseDropdown");
await expect(targetDatabaseDropdown).toBeVisible();
await targetDatabaseDropdown.click();
const targetDatabaseDropdownItem = await getDropdownItemByNameOrPosition(frame, { position: 0 });
await targetDatabaseDropdownItem.click();
// Step 14: Select first option from target container dropdown
const targetContainerDropdown = containerSelectionPanel.getByTestId("target-containerDropdown");
await expect(targetContainerDropdown).toBeVisible();
await targetContainerDropdown.click();
const targetContainerDropdownItem = await getDropdownItemByNameOrPosition(frame, { position: 0 });
await targetContainerDropdownItem.click();
// Step 15: Verify error message appears for same source and target selection
const errorContainer = containerSelectionPanel.getByTestId("Panel:ErrorContainer");
await expect(errorContainer).toBeVisible({ timeout: 5000 });
await expect(errorContainer).toContainText(/source.*target.*identical|same/i);
// Step 16: Select second option from target container dropdown to resolve error
await targetContainerDropdown.click();
const targetContainerDropdownItemSecond = await getDropdownItemByNameOrPosition(frame, { position: 1 });
await targetContainerDropdownItemSecond.click();
// Verify error message disappears
await expect(errorContainer).not.toBeVisible({ timeout: 5000 });
// Step 17: Click 'Next' button to proceed to preview
const nextButtonContainer = containerSelectionPanel.getByRole("button", { name: /next/i });
await expect(nextButtonContainer).toBeEnabled();
await nextButtonContainer.click();
// Step 18: Verify preview page displays selected details
const previewPanel = frame.locator("[data-testid*='preview'], [data-testid*='Preview']").first();
await expect(previewPanel).toBeVisible({ timeout: VISIBLE_TIMEOUT_MS });
// Verify preview displays source and target information
await expect(previewPanel.getByText(/source/i)).toBeVisible();
await expect(previewPanel.getByText(/target/i)).toBeVisible();
await expect(previewPanel.getByText(targetAccountName)).toBeVisible();
// Step 19: Enter valid job name
const jobNameInput = previewPanel.getByRole("textbox").or(previewPanel.getByTestId("job-name-input"));
await expect(jobNameInput).toBeVisible();
await jobNameInput.fill(expectedJobName);
// Step 20: Click 'Submit' button to create copy job
const submitButton = previewPanel.getByRole("button", { name: /submit/i });
await expect(submitButton).toBeEnabled();
// Set up API response interception
const apiResponsePromise = waitForApiResponse(
page,
`${targetAccountName}/dataTransferJobs/${expectedJobName}`,
"PUT"
);
await submitButton.click();
// Step 21: Wait for API response indicating successful job creation
await apiResponsePromise;
// Step 22: Verify job appears in jobs list
await expect(panel).not.toBeVisible({ timeout: 10000 }); // Panel should close
await expect(wrapper.getByText(expectedJobName)).toBeVisible({ timeout: VISIBLE_TIMEOUT_MS });
// Step 23: Verify side panel is closed after successful submission
await expect(frame.getByTestId("Panel:Create copy job")).not.toBeVisible();
});
test("1.2. Create Online Copy Job Successfully", async () => {
// Generate unique job name for this test
const onlineJobName = `online_job_${Date.now()}`;
// Step 1: Wait for the copy job screen to load
await expect(wrapper.getByTestId("CommandBar/Button:Create Copy Job")).toBeVisible({ timeout: VISIBLE_TIMEOUT_MS });
// Step 2: Click 'Create Copy Job' button
await wrapper.getByTestId("CommandBar/Button:Create Copy Job").click();
// Step 3: Verify side panel opens
panel = frame.getByTestId("Panel:Create copy job");
await expect(panel).toBeVisible({ timeout: VISIBLE_TIMEOUT_MS });
// Step 4: Select 'Online mode' radio button
const onlineModeRadio = panel.getByRole("radio", { name: /online/i });
await expect(onlineModeRadio).toBeVisible();
await expect(onlineModeRadio).toBeEnabled();
await onlineModeRadio.click();
await expect(onlineModeRadio).toBeChecked();
// Step 5: Verify online mode description is visible
const onlineModeDescription = panel.getByTestId("migration-type-description-online");
await expect(onlineModeDescription).toBeVisible();
await expect(onlineModeDescription).toContainText(/online/i);
// Step 6: Click 'Next' button
const nextButton = panel.getByRole("button", { name: /next/i });
await nextButton.click();
// Step 7: Verify permissions assignment panel is displayed
const permissionsPanel = frame.getByTestId("Panel:AssignPermissionsContainer");
await expect(permissionsPanel).toBeVisible({ timeout: VISIBLE_TIMEOUT_MS });
// Step 8: Complete permissions assignment process
// This step may involve clicking through permission assignment UI
const permissionsNextButton = permissionsPanel.getByRole("button", { name: /next|continue|proceed/i });
if (await permissionsNextButton.isVisible()) {
await permissionsNextButton.click();
}
// Step 9: Navigate to container selection screen
const containerSelectionPanel = frame.getByTestId("Panel:SelectSourceAndTargetContainers");
await expect(containerSelectionPanel).toBeVisible({ timeout: VISIBLE_TIMEOUT_MS });
// Step 10: Select source database and container
const sourceDatabaseDropdown = containerSelectionPanel.getByTestId("source-databaseDropdown");
await sourceDatabaseDropdown.click();
const sourceDatabaseDropdownItem = await getDropdownItemByNameOrPosition(frame, { position: 0 });
await sourceDatabaseDropdownItem.click();
const sourceContainerDropdown = containerSelectionPanel.getByTestId("source-containerDropdown");
await sourceContainerDropdown.click();
const sourceContainerDropdownItem = await getDropdownItemByNameOrPosition(frame, { position: 0 });
await sourceContainerDropdownItem.click();
// Step 11: Select different target database and container
const targetDatabaseDropdown = containerSelectionPanel.getByTestId("target-databaseDropdown");
await targetDatabaseDropdown.click();
const targetDatabaseDropdownItem = await getDropdownItemByNameOrPosition(frame, { position: 0 });
await targetDatabaseDropdownItem.click();
const targetContainerDropdown = containerSelectionPanel.getByTestId("target-containerDropdown");
await targetContainerDropdown.click();
const targetContainerDropdownItem = await getDropdownItemByNameOrPosition(frame, { position: 1 });
await targetContainerDropdownItem.click(); // Select different container
// Step 12: Navigate to preview screen
const nextButtonContainer = containerSelectionPanel.getByRole("button", { name: /next/i });
await expect(nextButtonContainer).toBeEnabled();
await nextButtonContainer.click();
// Step 13: Verify all selected details are accurate
const previewPanel = frame.locator("[data-testid*='preview'], [data-testid*='Preview']").first();
await expect(previewPanel).toBeVisible({ timeout: VISIBLE_TIMEOUT_MS });
// Verify online mode is indicated in preview
await expect(previewPanel.getByText(/online/i)).toBeVisible();
await expect(previewPanel.getByText(targetAccountName)).toBeVisible();
// Step 14: Enter valid job name
const jobNameInput = previewPanel.getByRole("textbox").or(previewPanel.getByTestId("job-name-input"));
await jobNameInput.fill(onlineJobName);
// Step 15: Submit copy job creation
const submitButton = previewPanel.getByRole("button", { name: /submit/i });
await expect(submitButton).toBeEnabled();
// Set up API response interception for online job creation
const apiResponsePromise = waitForApiResponse(
page,
`${targetAccountName}/dataTransferJobs/${onlineJobName}`,
"PUT"
);
await submitButton.click();
// Step 16: Verify successful job creation and list update
await apiResponsePromise;
// Verify panel closes and job appears in list
await expect(panel).not.toBeVisible({ timeout: 10000 });
await expect(wrapper.getByText(onlineJobName)).toBeVisible({ timeout: VISIBLE_TIMEOUT_MS });
// Verify online mode indicator in job list (if applicable)
const jobListItem = wrapper.locator(`[data-testid*="job-item"], tr, .job-row`).filter({ hasText: onlineJobName });
await expect(jobListItem).toBeVisible();
// Verify side panel is closed
await expect(frame.getByTestId("Panel:Create copy job")).not.toBeVisible();
});
});

View File

@@ -0,0 +1,32 @@
import { Frame, Locator, Page, test } from '@playwright/test';
import { ContainerCopy, getAccountName, TestAccount } from '../fx';
import { createMultipleTestContainers } from '../testData';
test.describe('Copy Job Seed', () => {
let page: Page;
let wrapper: Locator = null!;
let panel: Locator = null!;
let frame: Frame = null!;
let targetAccountName: string = "";
test.beforeAll("Copy Job - Before All", async ({ browser }) => {
await createMultipleTestContainers({ accountType: TestAccount.SQLContainerCopyOnly, containerCount: 3 });
page = await browser.newPage();
({ wrapper, frame } = await ContainerCopy.open(page, TestAccount.SQLContainerCopyOnly));
targetAccountName = getAccountName(TestAccount.SQLContainerCopyOnly);
});
test.afterAll("Copy Job - After All", async () => {
await page.unroute(/.*/, (route) => route.continue());
await page.close();
});
test.afterEach("Copy Job - After Each", async () => {
await page.unroute(/.*/, (route) => route.continue());
});
test('Successfully create a copy job for offline migration', async ({ page }) => {
// generate code here.
});
test('Successfully create a copy job for online migration', async ({ page }) => {
// generate code here.
});
});

View File

@@ -2,10 +2,12 @@ const { AzureCliCredential } = require("@azure/identity");
const { CosmosDBManagementClient } = require("@azure/arm-cosmosdb");
const ms = require("ms");
const subscriptionId = process.env["AZURE_SUBSCRIPTION_ID"];
const resourceGroupName = "de-e2e-tests";
// const subscriptionId = process.env["AZURE_SUBSCRIPTION_ID"];
// const resourceGroupName = "de-e2e-tests";
const subscriptionId = "074d02eb-4d74-486a-b299-b262264d1536";
const resourceGroupName = "bchoudhury-e2e-testing";
const thirtyMinutesAgo = new Date(Date.now() - 1000 * 60 * 30).getTime();
const thirtyMinutesAgo = new Date(Date.now() - 1000 * 60 * 5).getTime();
function friendlyTime(date) {
try {
@@ -18,7 +20,7 @@ function friendlyTime(date) {
async function main() {
const credentials = new AzureCliCredential();
const client = new CosmosDBManagementClient(credentials, subscriptionId);
const accounts = await client.databaseAccounts.list(resourceGroupName);
const accounts = await client.databaseAccounts.listByResourceGroup(resourceGroupName);
for (const account of accounts) {
if (account.name.endsWith("-readonly")) {
console.log(`SKIPPED: ${account.name}`);