1616 * SPDX-License-Identifier: Apache-2.0
1717 ***********************************************************************/
1818
19- import type { Locator } from '@playwright/test' ;
19+ /**
20+ * The 'test-audio-to-text.wav' file used in this test was sourced from the
21+ * whisper.cpp project (https://github.com/ggml-org/whisper.cpp).
22+ * It is licensed under the MIT License (see https://github.com/ggml-org/whisper.cpp/blob/master/LICENSE for details).
23+ * This specific WAV file is used solely for Playwright testing purposes within this repository.
24+ */
25+
26+ import type { APIResponse , Locator } from '@playwright/test' ;
2027import type { NavigationBar , ExtensionsPage } from '@podman-desktop/tests-playwright' ;
2128import {
2229 expect as playExpect ,
@@ -40,6 +47,9 @@ import {
4047 reopenAILabDashboard ,
4148 waitForExtensionToInitialize ,
4249} from './utils/aiLabHandler' ;
50+ import * as fs from 'node:fs' ;
51+ import * as path from 'node:path' ;
52+ import { fileURLToPath } from 'node:url' ;
4353
4454const AI_LAB_EXTENSION_OCI_IMAGE =
4555 process . env . EXTENSION_OCI_IMAGE ?? 'ghcr.io/containers/podman-desktop-extension-ai-lab:nightly' ;
@@ -52,6 +62,31 @@ const runnerOptions = {
5262 aiLabModelUploadDisabled : isWindows ? true : false ,
5363} ;
5464
65+ interface AiApp {
66+ appName : string ;
67+ appModel : string ;
68+ }
69+
70+ const AI_APPS : AiApp [ ] = [
71+ { appName : 'Audio to Text' , appModel : 'ggerganov/whisper.cpp' } ,
72+ { appName : 'ChatBot' , appModel : 'ibm-granite/granite-3.3-8b-instruct-GGUF' } ,
73+ { appName : 'Summarizer' , appModel : 'ibm-granite/granite-3.3-8b-instruct-GGUF' } ,
74+ { appName : 'Code Generation' , appModel : 'ibm-granite/granite-3.3-8b-instruct-GGUF' } ,
75+ { appName : 'RAG Chatbot' , appModel : 'ibm-granite/granite-3.3-8b-instruct-GGUF' } ,
76+ { appName : 'Function calling' , appModel : 'ibm-granite/granite-3.3-8b-instruct-GGUF' } ,
77+ ] ;
78+
79+ const __filename = fileURLToPath ( import . meta. url ) ;
80+ const __dirname = path . dirname ( __filename ) ;
81+ const TEST_AUDIO_FILE_PATH : string = path . resolve (
82+ __dirname ,
83+ '..' ,
84+ '..' ,
85+ 'playwright' ,
86+ 'resources' ,
87+ `test-audio-to-text.wav` ,
88+ ) ;
89+
5590test . use ( {
5691 runnerOptions : new RunnerOptions ( runnerOptions ) ,
5792} ) ;
@@ -476,7 +511,7 @@ test.describe.serial(`AI Lab extension installation and verification`, () => {
476511 } ) ;
477512 } ) ;
478513
479- [ 'Audio to Text' , 'ChatBot' , 'Summarizer' , 'Code Generation' , 'RAG Chatbot' , 'Function calling' ] . forEach ( appName => {
514+ AI_APPS . forEach ( ( { appName, appModel } ) => {
480515 test . describe . serial ( `AI Recipe installation` , ( ) => {
481516 test . skip (
482517 ! process . env . EXT_TEST_RAG_CHATBOT && appName === 'RAG Chatbot' ,
@@ -500,41 +535,71 @@ test.describe.serial(`AI Lab extension installation and verification`, () => {
500535 } ) ;
501536
502537 test ( `Verify that model service for the ${ appName } is working` , async ( { request } ) => {
503- test . skip ( appName !== 'Function calling' ) ;
538+ test . skip ( appName !== 'Function calling' && appName !== 'Audio to Text' ) ;
539+ test . fail (
540+ appName === 'Audio to Text' ,
541+ 'Expected failure due to issue #3111: https://github.com/containers/podman-desktop-extension-ai-lab/issues/3111' ,
542+ ) ;
504543 test . setTimeout ( 600_000 ) ;
505544
506545 const modelServicePage = await aiLabPage . navigationBar . openServices ( ) ;
507- const serviceDetailsPage = await modelServicePage . openServiceDetails (
508- 'ibm-granite/granite-3.3-8b-instruct-GGUF' ,
509- ) ;
546+ const serviceDetailsPage = await modelServicePage . openServiceDetails ( appModel ) ;
510547
511548 await playExpect
512549 // eslint-disable-next-line sonarjs/no-nested-functions
513550 . poll ( async ( ) => await serviceDetailsPage . getServiceState ( ) , { timeout : 60_000 } )
514551 . toBe ( 'RUNNING' ) ;
552+
515553 const port = await serviceDetailsPage . getInferenceServerPort ( ) ;
516- const url = `http://localhost:${ port } /v1/chat/completions ` ;
554+ const baseUrl = `http://localhost:${ port } ` ;
517555
518- const response = await request . post ( url , {
519- data : {
520- messages : [
521- {
522- content : 'You are a helpful assistant.' ,
523- role : 'system' ,
556+ let response : APIResponse ;
557+ let expectedResponse : string ;
558+
559+ switch ( appModel ) {
560+ case 'ggerganov/whisper.cpp' : {
561+ expectedResponse =
562+ 'And so my fellow Americans, ask not what your country can do for you, ask what you can do for your country' ;
563+ const audioFileContent = fs . readFileSync ( TEST_AUDIO_FILE_PATH ) ;
564+
565+ response = await request . post ( `${ baseUrl } /inference` , {
566+ headers : {
567+ Accept : 'application/json' ,
568+ } ,
569+ multipart : {
570+ file : {
571+ name : 'test.wav' ,
572+ mimeType : 'audio/wav' ,
573+ buffer : audioFileContent ,
574+ } ,
524575 } ,
525- {
526- content : 'What is the capital of Czech Republic?' ,
527- role : 'user' ,
576+ timeout : 600_000 ,
577+ } ) ;
578+ break ;
579+ }
580+
581+ case 'ibm-granite/granite-3.3-8b-instruct-GGUF' : {
582+ expectedResponse = 'Prague' ;
583+ response = await request . post ( `${ baseUrl } /v1/chat/completions` , {
584+ data : {
585+ messages : [
586+ { role : 'system' , content : 'You are a helpful assistant.' } ,
587+ { role : 'user' , content : 'What is the capital of Czech Republic?' } ,
588+ ] ,
528589 } ,
529- ] ,
530- } ,
531- timeout : 600_000 ,
532- } ) ;
590+ timeout : 600_000 ,
591+ } ) ;
592+ break ;
593+ }
594+
595+ default :
596+ throw new Error ( `Unhandled model type: ${ appModel } ` ) ;
597+ }
533598
534599 playExpect ( response . ok ( ) ) . toBeTruthy ( ) ;
535600 const body = await response . body ( ) ;
536601 const text = body . toString ( ) ;
537- playExpect ( text ) . toContain ( 'Prague' ) ;
602+ playExpect ( text ) . toContain ( expectedResponse ) ;
538603 } ) ;
539604
540605 test ( `${ appName } : Restart, Stop, Delete. Clean up model service` , async ( ) => {
0 commit comments