Skip to content

Commit 783b7c0

Browse files
inhaz1izaheen@anaconda.com
andauthored
test(e2e): extend Anaconda AI CLI coverage and bump playwright-utils (#115)
Add version command coverage, refine server lifecycle steps, and update @anaconda/playwright-utils to 1.3.6 with lockfile refresh. Made-with: Cursor Co-authored-by: izaheen@anaconda.com <izaheen@192.168.1.21>
1 parent 8bc7934 commit 783b7c0

8 files changed

Lines changed: 181 additions & 159 deletions

File tree

package-lock.json

Lines changed: 108 additions & 104 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"cli"
1111
],
1212
"devDependencies": {
13-
"@anaconda/playwright-utils": "1.2.4",
13+
"@anaconda/playwright-utils": "1.3.6",
1414
"@playwright/test": "^1.49.0",
1515
"cross-env": "^7.0.3",
1616
"dotenv": "^16.4.5",

tests/e2e/fixtures/fixture.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { AnacondaAiCli } from '@pages/cli/anaconda-ai-cmds';
44
export const test = baseTest.extend<{
55
anacondaAiCli: AnacondaAiCli;
66
}>({
7-
anacondaAiCli: async ({ }, use) => {
7+
anacondaAiCli: async ({}, use) => {
88
await use(new AnacondaAiCli());
99
},
1010
});

tests/e2e/pages/cli/anaconda-ai-cmds.ts

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { shellCommand, stripAnsiSgrAndTrim, verifyShellExitCode, type ShellResult } from 'tests/utils/CliUtils';
1+
import { type ShellResult, shellCommand, stripAnsiSgrAndTrim, verifyShellExitCode } from 'tests/utils/CliUtils';
22
import * as cliCommands from './cliCommands';
33
import { expect } from 'tests/test-setup/page-setup';
44
import { INVALID_MODEL_ERROR_MESSAGE, ModelApi, ServerApi } from '@testdata/model-api';
@@ -26,10 +26,7 @@ export class AnacondaAiCli {
2626
verifyShellExitCode(result, 'anaconda ai models --json');
2727

2828
const models = JSON.parse(stripAnsiSgrAndTrim(result.output)) as ModelApi[];
29-
expect(
30-
Array.isArray(models) && models.length,
31-
'models output should be a non-empty array',
32-
).toBeGreaterThan(0);
29+
expect(Array.isArray(models) && models.length, 'models output should be a non-empty array').toBeGreaterThan(0);
3330
this.assertModelResponseData(models);
3431
}
3532

@@ -59,7 +56,7 @@ export class AnacondaAiCli {
5956

6057
const servers = JSON.parse(stripAnsiSgrAndTrim(result.output)) as ServerApi[];
6158
const expectedModelFile = `${modelName}_${modelQuantization}.gguf`;
62-
const server = servers.find((s) => s.model === expectedModelFile);
59+
const server = servers.find(s => s.model === expectedModelFile);
6360

6461
expect(server, `Expected server for model "${expectedModelFile}" to appear in servers list`).toBeDefined();
6562
expect(server!.server_id, `Expected server_id to contain model name "${modelName}"`).toContain(modelName);
@@ -90,10 +87,9 @@ export class AnacondaAiCli {
9087
}
9188

9289
public verifyInvalidDownloadModelCommand(result: ShellResult): void {
93-
expect(
94-
result.exitCode,
95-
`Expected invalid download command to fail, but got exit code ${result.exitCode}`,
96-
).not.toBe(0);
90+
expect(result.exitCode, `Expected invalid download command to fail, but got exit code ${result.exitCode}`).not.toBe(
91+
0,
92+
);
9793
const output = stripAnsiSgrAndTrim(result.output).toLowerCase();
9894
expect(output, 'Expected the model to be invalid').toContain('error');
9995
expect(output, 'Expected output should contain invalid model error message').toContain(INVALID_MODEL_ERROR_MESSAGE);
@@ -122,10 +118,7 @@ export class AnacondaAiCli {
122118
const output = stripAnsiSgrAndTrim(result.output).toLowerCase();
123119
const expectedModelFile = `${modelName}_${modelQuantization}.gguf`.toLowerCase();
124120
expect(output.includes('running'), 'Expected status to be "running"').toBeTruthy();
125-
expect(
126-
output.includes('inference/serve/'),
127-
'Expected "inference/serve/" is running',
128-
).toBeTruthy();
121+
expect(output.includes('inference/serve/'), 'Expected "inference/serve/" is running').toBeTruthy();
129122
expect(
130123
output.includes(expectedModelFile),
131124
`Expected model "${expectedModelFile}" to appear in output`,
@@ -202,11 +195,34 @@ export class AnacondaAiCli {
202195
expect(model.trained_for, `models[${modelIndex}].trained_for should be defined`).toBeDefined();
203196

204197
model.quantizations.forEach((quant, quantIndex) => {
205-
expect(quant.method, `models[${modelIndex}].quantizations[${quantIndex}].method should be defined`).toBeDefined();
206-
expect(quant.running, `models[${modelIndex}].quantizations[${quantIndex}].running should be defined`).not.toBeUndefined();
207-
expect(quant.downloaded, `models[${modelIndex}].quantizations[${quantIndex}].downloaded should be defined`).not.toBeUndefined();
208-
expect(quant.blocked, `models[${modelIndex}].quantizations[${quantIndex}].blocked should be defined`).not.toBeUndefined();
198+
expect(
199+
quant.method,
200+
`models[${modelIndex}].quantizations[${quantIndex}].method should be defined`,
201+
).toBeDefined();
202+
expect(
203+
quant.running,
204+
`models[${modelIndex}].quantizations[${quantIndex}].running should be defined`,
205+
).toBeDefined();
206+
expect(
207+
quant.downloaded,
208+
`models[${modelIndex}].quantizations[${quantIndex}].downloaded should be defined`,
209+
).toBeDefined();
210+
expect(
211+
quant.blocked,
212+
`models[${modelIndex}].quantizations[${quantIndex}].blocked should be defined`,
213+
).toBeDefined();
209214
});
210215
});
211216
}
217+
218+
public async runAnacondaAiVersionCommand(): Promise<ShellResult> {
219+
return await shellCommand(cliCommands.anacondaAiVersionCmd);
220+
}
221+
222+
public verifyAnacondaAiVersionCommand(result: ShellResult): void {
223+
verifyShellExitCode(result, 'anaconda ai --version');
224+
225+
const output = stripAnsiSgrAndTrim(result.output).toLowerCase();
226+
expect(output.includes('anaconda-ai'), 'Expected "anaconda-ai" to be in output').toBeTruthy();
227+
}
212228
}

tests/e2e/pages/cli/anaconda-ai-setup.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { expect } from '@playwright/test';
2-
import { shellCommand, verifyShellExitCode, type ShellResult } from 'tests/utils/CliUtils';
2+
import { type ShellResult, shellCommand, verifyShellExitCode } from 'tests/utils/CliUtils';
33

44
import * as cliCommands from './cliCommands';
55

@@ -12,8 +12,7 @@ export class AnacondaAiSetupCli {
1212
public verifyInstallAiPackageCommand(result: ShellResult): void {
1313
verifyShellExitCode(result, 'install command');
1414
const output = result.output.toLowerCase();
15-
const isInstalledNow =
16-
output.includes('executing transaction') && output.includes('anaconda-ai');
15+
const isInstalledNow = output.includes('executing transaction') && output.includes('anaconda-ai');
1716
const isAlreadyInstalled = output.includes('all requested packages already installed');
1817

1918
expect(
@@ -38,7 +37,7 @@ export class AnacondaAiSetupCli {
3837
verifyShellExitCode(result, 'sites list command');
3938
}
4039

41-
// True if `anaconda sites list` output contains the given site name.
40+
// True if `anaconda sites list` output contains the given site name.
4241
public isSiteNameListed(listResult: ShellResult, name: string): boolean {
4342
const output = `${listResult.output}\n${listResult.stderrOutput}`;
4443
return output.includes(`│ ${name} `);

tests/e2e/pages/cli/cliCommands.ts

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,9 @@ const anacondaAiChannel = process.env.ANACONDA_AI_CHANNEL ?? 'anaconda-cloud/lab
44
const anacondaAiVersion = process.env.ANACONDA_AI_VERSION ?? '0.5.0';
55

66
// Run all command in the anaconda-cli environment
7-
const condaRun = (inner: string): string =>
8-
`conda run -n anaconda-cli --no-capture-output ${inner}`;
7+
const condaRun = (inner: string): string => `conda run -n anaconda-cli --no-capture-output ${inner}`;
98

10-
export const installAiPackageCmd =
11-
`conda create -c defaults -c conda-forge ${anacondaAiChannel}::anaconda-ai=${anacondaAiVersion} -n anaconda-cli --yes`;
9+
export const installAiPackageCmd = `conda create -c defaults -c conda-forge ${anacondaAiChannel}::anaconda-ai=${anacondaAiVersion} -n anaconda-cli --yes`;
1210

1311
// Verify the Anaconda AI package environment is runnable
1412
export const activateAiPackageEnvCmd = condaRun('conda list anaconda-ai');
@@ -21,20 +19,19 @@ export const addSiteCmd = (domain: string, name: string): string =>
2119
export const modifySiteCmd = (domain: string, name: string): string =>
2220
condaRun(`anaconda sites modify --domain ${domain} --name ${name} --default --yes`);
2321

24-
export const configureAiPackageEnvToUseSandboxCmd = condaRun(
25-
'anaconda ai config --backend ai-catalyst --yes',
26-
);
22+
export const configureAiPackageEnvToUseSandboxCmd = condaRun('anaconda ai config --backend ai-catalyst --yes');
2723

2824
export const authWhoamiCmd = condaRun('anaconda auth whoami');
2925

3026
export const anacondaAiHelpCmd = condaRun('anaconda ai --help');
3127

28+
// Anaconda AI Version Command
29+
export const anacondaAiVersionCmd = condaRun('anaconda ai version --json');
30+
3231
// Anaconda AI Models Command
3332
export const anacondaAiModelsListCmd = condaRun('anaconda ai models --json');
3433

35-
export const anacondaAiBlockedModelsListCmd = condaRun(
36-
'anaconda ai models --show-blocked --json',
37-
);
34+
export const anacondaAiBlockedModelsListCmd = condaRun('anaconda ai models --show-blocked --json');
3835

3936
// Download Model Command
4037
export const downloadModelCmd = (modelName: string, modelQuantization: string): string =>

tests/e2e/specs/cli/anaconda-ai.spec.ts

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ import {
77
} from '@testdata/model-api';
88

99
test.describe('Anaconda AI CLI Commands @anaconda-ai', () => {
10+
test('anaconda ai version command', async ({ anacondaAiCli }) => {
11+
const result = await anacondaAiCli.runAnacondaAiVersionCommand();
12+
anacondaAiCli.verifyAnacondaAiVersionCommand(result);
13+
});
14+
1015
test('anaconda ai --help', async ({ anacondaAiCli }) => {
1116
const result = await anacondaAiCli.runAnacondaAiHelpCommand();
1217
anacondaAiCli.verifyAnacondaAiHelpCommand(result);
@@ -31,10 +36,7 @@ test.describe('Anaconda AI CLI Commands @anaconda-ai', () => {
3136
});
3237

3338
test('anaconda ai download invalid model command', async ({ anacondaAiCli }) => {
34-
const result = await anacondaAiCli.runDownloadModelCommand(
35-
INVALID_MODEL_NAME,
36-
INVALID_MODEL_QUANTIZATION,
37-
);
39+
const result = await anacondaAiCli.runDownloadModelCommand(INVALID_MODEL_NAME, INVALID_MODEL_QUANTIZATION);
3840
anacondaAiCli.verifyInvalidDownloadModelCommand(result);
3941
});
4042

@@ -44,57 +46,59 @@ test.describe('Anaconda AI CLI Commands @anaconda-ai', () => {
4446
});
4547

4648
test('anaconda ai server lifecycle: launch, verify, stop, and delete a model server', async ({ anacondaAiCli }) => {
47-
await test.step('step 1: launch model server and verify it is running', async () => {
49+
await test.step('1: launch model server and verify it is running', async () => {
4850
const launchResult = await anacondaAiCli.runLaunchModelCommand(
4951
DOWNLOAD_TEST_MODEL_NAME,
5052
DOWNLOAD_TEST_MODEL_QUANTIZATION,
5153
);
5254
anacondaAiCli.verifyLaunchModelCommand(launchResult, DOWNLOAD_TEST_MODEL_NAME, DOWNLOAD_TEST_MODEL_QUANTIZATION);
5355
});
5456

55-
await test.step('step 2: verify server appears in servers list with status "running"', async () => {
57+
await test.step('2: verify server appears in servers list with status "running"', async () => {
5658
const serversResult = await anacondaAiCli.runAnacondaAiServersListCommand();
57-
anacondaAiCli.verifyRunningServerInList(serversResult, DOWNLOAD_TEST_MODEL_NAME, DOWNLOAD_TEST_MODEL_QUANTIZATION);
59+
anacondaAiCli.verifyRunningServerInList(
60+
serversResult,
61+
DOWNLOAD_TEST_MODEL_NAME,
62+
DOWNLOAD_TEST_MODEL_QUANTIZATION,
63+
);
5864
});
5965

60-
await test.step('step 3: launching the same server again returns AnacondaAIException', async () => {
66+
await test.step('3: launching the same server again returns AnacondaAIException', async () => {
6167
const duplicateResult = await anacondaAiCli.runLaunchModelCommand(
6268
DOWNLOAD_TEST_MODEL_NAME,
6369
DOWNLOAD_TEST_MODEL_QUANTIZATION,
6470
);
6571
anacondaAiCli.verifyDuplicateLaunchModelCommand(duplicateResult);
6672
});
6773

68-
await test.step('step 4: stop the model server and verify it is stopped', async () => {
74+
await test.step('4: stop the model server and verify it is stopped', async () => {
6975
const stopResult = await anacondaAiCli.runStopModelCommand(
7076
DOWNLOAD_TEST_MODEL_NAME,
7177
DOWNLOAD_TEST_MODEL_QUANTIZATION,
7278
);
7379
anacondaAiCli.verifyStopModelCommand(stopResult, DOWNLOAD_TEST_MODEL_NAME, DOWNLOAD_TEST_MODEL_QUANTIZATION);
7480
});
7581

76-
await test.step('step 5: delete the model server and verify it is removed', async () => {
82+
await test.step('5: delete the model server and verify it is removed', async () => {
7783
const deleteResult = await anacondaAiCli.runStopAndRemoveModelCommand(
7884
DOWNLOAD_TEST_MODEL_NAME,
7985
DOWNLOAD_TEST_MODEL_QUANTIZATION,
8086
);
81-
anacondaAiCli.verifyStopAndRemoveModelCommand(deleteResult, DOWNLOAD_TEST_MODEL_NAME, DOWNLOAD_TEST_MODEL_QUANTIZATION);
87+
anacondaAiCli.verifyStopAndRemoveModelCommand(
88+
deleteResult,
89+
DOWNLOAD_TEST_MODEL_NAME,
90+
DOWNLOAD_TEST_MODEL_QUANTIZATION,
91+
);
8292
});
8393
});
8494

8595
test('anaconda ai launch - invalid format returns ValueError', async ({ anacondaAiCli }) => {
86-
const result = await anacondaAiCli.runLaunchModelCommand(
87-
INVALID_MODEL_NAME,
88-
INVALID_MODEL_QUANTIZATION,
89-
);
96+
const result = await anacondaAiCli.runLaunchModelCommand(INVALID_MODEL_NAME, INVALID_MODEL_QUANTIZATION);
9097
anacondaAiCli.verifyLaunchModelInvalidFormatCommand(result);
9198
});
9299

93100
test('anaconda ai launch - unknown model returns ModelNotFound', async ({ anacondaAiCli }) => {
94-
const result = await anacondaAiCli.runLaunchModelCommand(
95-
INVALID_MODEL_NAME,
96-
DOWNLOAD_TEST_MODEL_QUANTIZATION,
97-
);
101+
const result = await anacondaAiCli.runLaunchModelCommand(INVALID_MODEL_NAME, DOWNLOAD_TEST_MODEL_QUANTIZATION);
98102
anacondaAiCli.verifyLaunchModelNotFoundCommand(result);
99103
});
100104
});

tests/test-setup/global-setup.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
* Runs the Anaconda AI package install before any tests.
44
*/
55
import { AnacondaAiSetupCli } from '../e2e/pages/cli/anaconda-ai-setup';
6-
import { baseDomain, SELF_HOSTED_SITE_NAME } from '@testdata/site-data';
6+
import { SELF_HOSTED_SITE_NAME, baseDomain } from '@testdata/site-data';
77

88
async function setupAnacondaAi(cli: AnacondaAiSetupCli): Promise<void> {
9-
console.log(`[Global Setup] Installing and verifying Anaconda AI in "anaconda-cli" (version: ${process.env.ANACONDA_AI_VERSION ?? 'not set'})`,);
9+
console.log(
10+
`[Global Setup] Installing and verifying Anaconda AI in "anaconda-cli" (version: ${process.env.ANACONDA_AI_VERSION ?? 'not set'})`,
11+
);
1012

1113
// Install the Anaconda AI package
1214
const installResult = await cli.runInstallAiPackageCommand();

0 commit comments

Comments
 (0)