Skip to content

Commit 322b00e

Browse files
chore(test): add e2e test to verify llama stack (#3486)
Signed-off-by: serbangeorge-m <[email protected]>
1 parent 1b60cde commit 322b00e

File tree

3 files changed

+133
-0
lines changed

3 files changed

+133
-0
lines changed

tests/playwright/src/ai-lab-extension.spec.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ import * as fs from 'node:fs';
5656
import * as path from 'node:path';
5757
import { fileURLToPath } from 'node:url';
5858
import type { AILabTryInstructLabPage } from './model/ai-lab-try-instructlab-page';
59+
import type { AiLlamaStackPage } from './model/ai-lab-model-llamastack-page';
5960

6061
const AI_LAB_EXTENSION_OCI_IMAGE =
6162
process.env.EXTENSION_OCI_IMAGE ?? 'ghcr.io/containers/podman-desktop-extension-ai-lab:nightly';
@@ -188,6 +189,79 @@ test.describe.serial(`AI Lab extension installation and verification`, () => {
188189
);
189190
});
190191

192+
test.describe.serial(`Start Llama Stack from sidebar and verify containers`, { tag: '@smoke' }, () => {
193+
let llamaStackPage: AiLlamaStackPage;
194+
const llamaStackContainerNames: string[] = [];
195+
196+
test.beforeAll(`Open Llama Stack`, async ({ runner, page, navigationBar }) => {
197+
aiLabPage = await reopenAILabDashboard(runner, page, navigationBar);
198+
await aiLabPage.navigationBar.waitForLoad();
199+
llamaStackPage = await aiLabPage.navigationBar.openLlamaStack();
200+
await llamaStackPage.waitForLoad();
201+
});
202+
203+
test(`Start Llama Stack containers`, async () => {
204+
test.setTimeout(300_000);
205+
await llamaStackPage.waitForLoad();
206+
await llamaStackPage.runLlamaStackContainer();
207+
await playExpect(llamaStackPage.openLlamaStackContainerButton).toBeVisible({ timeout: 120_000 });
208+
await playExpect(llamaStackPage.exploreLlamaStackEnvironmentButton).toBeVisible({ timeout: 120_000 });
209+
await playExpect(llamaStackPage.openLlamaStackContainerButton).toBeEnabled({ timeout: 30_000 });
210+
await playExpect(llamaStackPage.exploreLlamaStackEnvironmentButton).toBeEnabled({ timeout: 30_000 });
211+
});
212+
213+
test(`Verify Llama Stack containers are running`, async ({ navigationBar }) => {
214+
let containersPage = await navigationBar.openContainers();
215+
await playExpect(containersPage.heading).toBeVisible();
216+
217+
await playExpect
218+
.poll(
219+
async () => {
220+
const allRows = await containersPage.getAllTableRows();
221+
llamaStackContainerNames.length = 0;
222+
for (const row of allRows) {
223+
const text = await row.textContent();
224+
if (text?.includes('llama-stack')) {
225+
const containerNameMatch = RegExp(/\b(llama-stack[^\s]*)/).exec(text);
226+
if (containerNameMatch) {
227+
llamaStackContainerNames.push(containerNameMatch[1]);
228+
}
229+
}
230+
}
231+
return llamaStackContainerNames.length;
232+
},
233+
{
234+
timeout: 30_000,
235+
intervals: [5_000],
236+
},
237+
)
238+
.toBe(2);
239+
240+
console.log(`Found containers: ${llamaStackContainerNames.join(', ')}`);
241+
242+
for (const container of llamaStackContainerNames) {
243+
containersPage = await navigationBar.openContainers();
244+
await playExpect(containersPage.heading).toBeVisible();
245+
const containersDetailsPage = await containersPage.openContainersDetails(container);
246+
await playExpect(containersDetailsPage.heading).toBeVisible();
247+
await playExpect
248+
.poll(async () => containersDetailsPage.getState(), { timeout: 30_000 })
249+
.toContain(ContainerState.Running);
250+
}
251+
});
252+
253+
test.afterAll(`Stop Llama Stack containers`, async ({ navigationBar }) => {
254+
for (const container of llamaStackContainerNames) {
255+
const containersPage = await navigationBar.openContainers();
256+
await playExpect(containersPage.heading).toBeVisible();
257+
await containersPage.deleteContainer(container);
258+
await playExpect
259+
.poll(async () => await containersPage.containerExists(container), { timeout: 30_000 })
260+
.toBeFalsy();
261+
}
262+
});
263+
});
264+
191265
test.describe.serial('AI Lab API endpoint e2e test', { tag: '@smoke' }, () => {
192266
let localServerPort: string;
193267
let extensionVersion: string | undefined;
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/**********************************************************************
2+
* Copyright (C) 2024 Red Hat, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* SPDX-License-Identifier: Apache-2.0
17+
***********************************************************************/
18+
import type { Locator, Page } from '@playwright/test';
19+
import { AILabBasePage } from './ai-lab-base-page';
20+
21+
export class AiLlamaStackPage extends AILabBasePage {
22+
readonly startLlamaStackContainerButton: Locator;
23+
readonly openLlamaStackContainerButton: Locator;
24+
readonly exploreLlamaStackEnvironmentButton: Locator;
25+
26+
constructor(page: Page, webview: Page) {
27+
super(page, webview, 'Llama Stack');
28+
this.startLlamaStackContainerButton = this.webview.getByRole('button', { name: 'Start Llama Stack container' });
29+
this.openLlamaStackContainerButton = this.webview.getByRole('button', { name: 'Open Llama Stack container' });
30+
this.exploreLlamaStackEnvironmentButton = this.webview.getByRole('button', {
31+
name: 'Explore Llama-Stack environment',
32+
});
33+
}
34+
35+
async waitForLoad(): Promise<void> {
36+
await this.startLlamaStackContainerButton.waitFor({ state: 'visible' });
37+
}
38+
39+
async runLlamaStackContainer(): Promise<void> {
40+
await this.startLlamaStackContainerButton.click();
41+
}
42+
43+
async waitForOpenLlamaStackContainerButton(): Promise<void> {
44+
await this.openLlamaStackContainerButton.waitFor({ state: 'visible' });
45+
}
46+
47+
async waitForExploreLlamaStackEnvironmentButton(): Promise<void> {
48+
await this.exploreLlamaStackEnvironmentButton.waitFor({ state: 'visible' });
49+
}
50+
}

tests/playwright/src/model/ai-lab-navigation-bar.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { AILabPlaygroundsPage } from './ai-lab-playgrounds-page';
2727
import { AILabLocalServerPage } from './ai-lab-local-server-page';
2828
import { AILabDashboardPage } from './ai-lab-dashboard-page';
2929
import { AILabTryInstructLabPage } from './ai-lab-try-instructlab-page';
30+
import { AiLlamaStackPage } from './ai-lab-model-llamastack-page';
3031

3132
export class AILabNavigationBar extends AILabBasePage {
3233
readonly navigationBar: Locator;
@@ -36,6 +37,7 @@ export class AILabNavigationBar extends AILabBasePage {
3637
readonly catalogButton: Locator;
3738
readonly servicesButton: Locator;
3839
readonly playgroundsButton: Locator;
40+
readonly llamaStackButton: Locator;
3941
readonly tuneButton: Locator;
4042
readonly localServerButton: Locator;
4143
readonly tryInstructLabButton: Locator;
@@ -49,6 +51,7 @@ export class AILabNavigationBar extends AILabBasePage {
4951
this.catalogButton = this.navigationBar.getByRole('link', { name: 'Catalog', exact: true });
5052
this.servicesButton = this.navigationBar.getByRole('link', { name: 'Services' });
5153
this.playgroundsButton = this.navigationBar.getByRole('link', { name: 'Playgrounds' });
54+
this.llamaStackButton = this.navigationBar.getByRole('link', { name: 'Llama Stack' });
5255
this.tuneButton = this.navigationBar.getByRole('link', { name: 'Tune with InstructLab' });
5356
this.localServerButton = this.navigationBar.getByRole('link', { name: 'Local Server' });
5457
this.tryInstructLabButton = this.navigationBar.getByRole('link', { name: 'Try InstructLab' });
@@ -94,6 +97,12 @@ export class AILabNavigationBar extends AILabBasePage {
9497
return new AILabPlaygroundsPage(this.page, this.webview);
9598
}
9699

100+
async openLlamaStack(): Promise<AiLlamaStackPage> {
101+
await playExpect(this.llamaStackButton).toBeEnabled();
102+
await this.llamaStackButton.click();
103+
return new AiLlamaStackPage(this.page, this.webview);
104+
}
105+
97106
async openLocalServer(): Promise<AILabLocalServerPage> {
98107
await playExpect(this.localServerButton).toBeEnabled();
99108
await this.localServerButton.click();

0 commit comments

Comments
 (0)