11import { expect } from 'chai' ;
22
3+ import clipboard from 'clipboardy' ;
34import type { CompassBrowser } from '../helpers/compass-browser' ;
45import { startTelemetryServer } from '../helpers/telemetry' ;
56import type { Telemetry } from '../helpers/telemetry' ;
@@ -17,6 +18,13 @@ import { startMockAssistantServer } from '../helpers/assistant-service';
1718import type { MockAssistantResponse } from '../helpers/assistant-service' ;
1819import { isTestingWeb } from '../helpers/test-runner-context' ;
1920
21+ import { context } from '../helpers/test-runner-context' ;
22+
23+ type Message = {
24+ text : string ;
25+ role : 'assistant' | 'user' ;
26+ } ;
27+
2028describe ( 'MongoDB Assistant' , function ( ) {
2129 let compass : Compass ;
2230 let browser : CompassBrowser ;
@@ -68,8 +76,7 @@ describe('MongoDB Assistant', function () {
6876 const chatInput = browser . $ ( Selectors . AssistantChatInputTextArea ) ;
6977 await chatInput . waitForDisplayed ( ) ;
7078 await chatInput . setValue ( text ) ;
71- const submitButton = browser . $ ( Selectors . AssistantChatSubmitButton ) ;
72- await submitButton . click ( ) ;
79+ await browser . clickVisible ( Selectors . AssistantChatSubmitButton ) ;
7380
7481 switch ( expectedResult ) {
7582 case 'success' :
@@ -179,13 +186,6 @@ describe('MongoDB Assistant', function () {
179186 } ) ;
180187
181188 describe ( 'drawer visibility' , function ( ) {
182- before ( function ( ) {
183- skipForWeb (
184- this ,
185- 'E2E testing for assistant drawer visibility on compass-web is not yet implemented'
186- ) ;
187- } ) ;
188-
189189 it ( 'shows the assistant drawer button when AI features are enabled' , async function ( ) {
190190 await setAIFeatures ( true ) ;
191191
@@ -195,6 +195,12 @@ describe('MongoDB Assistant', function () {
195195 } ) ;
196196
197197 it ( 'does not show the assistant drawer button when AI features are disabled' , async function ( ) {
198+ // we cannot opt back out on web because it is stored server-side
199+ skipForWeb (
200+ this ,
201+ 'E2E testing for assistant drawer visibility on compass-web is not yet implemented'
202+ ) ;
203+
198204 await setAIFeatures ( false ) ;
199205
200206 const drawerButton = browser . $ ( Selectors . AssistantDrawerButton ) ;
@@ -219,6 +225,7 @@ describe('MongoDB Assistant', function () {
219225 } ) ;
220226
221227 describe ( 'before opt-in' , function ( ) {
228+ // we cannot opt back out on web because it is stored server-side
222229 before ( async function ( ) {
223230 skipForWeb (
224231 this ,
@@ -259,9 +266,7 @@ describe('MongoDB Assistant', function () {
259266 await optInModal . waitForDisplayed ( ) ;
260267 expect ( await optInModal . isDisplayed ( ) ) . to . be . true ;
261268
262- const declineLink = browser . $ ( Selectors . AIOptInModalDeclineLink ) ;
263- await declineLink . waitForDisplayed ( ) ;
264- await declineLink . click ( ) ;
269+ await browser . clickVisible ( Selectors . AIOptInModalDeclineLink ) ;
265270
266271 await optInModal . waitForDisplayed ( { reverse : true } ) ;
267272
@@ -275,9 +280,7 @@ describe('MongoDB Assistant', function () {
275280 await optInModal . waitForDisplayed ( ) ;
276281 expect ( await optInModal . isDisplayed ( ) ) . to . be . true ;
277282
278- const declineLink = browser . $ ( Selectors . AIOptInModalDeclineLink ) ;
279- await declineLink . waitForDisplayed ( ) ;
280- await declineLink . click ( ) ;
283+ await browser . clickVisible ( Selectors . AIOptInModalDeclineLink ) ;
281284
282285 await optInModal . waitForDisplayed ( { reverse : true } ) ;
283286
@@ -288,6 +291,7 @@ describe('MongoDB Assistant', function () {
288291
289292 describe ( 'opting in' , function ( ) {
290293 before ( async function ( ) {
294+ // we cannot opt back out on web because it is stored server-side
291295 skipForWeb (
292296 this ,
293297 'E2E testing for opt-in on compass-web is not yet implemented'
@@ -303,16 +307,14 @@ describe('MongoDB Assistant', function () {
303307 await optInModal . waitForDisplayed ( ) ;
304308 expect ( await optInModal . isDisplayed ( ) ) . to . be . true ;
305309
306- const acceptButton = browser . $ ( Selectors . AIOptInModalAcceptButton ) ;
307- await acceptButton . waitForClickable ( ) ;
308- await acceptButton . click ( ) ;
310+ await browser . clickVisible ( Selectors . AIOptInModalAcceptButton ) ;
309311
310312 await optInModal . waitForDisplayed ( { reverse : true } ) ;
311313
312314 const chatInput = browser . $ ( Selectors . AssistantChatInputTextArea ) ;
313315 expect ( await chatInput . getValue ( ) ) . to . equal ( '' ) ;
314316
315- expect ( await getDisplayedMessages ( browser ) ) . to . deep . equal ( [
317+ await waitForMessages ( browser , [
316318 { text : testMessage , role : 'user' } ,
317319 { text : testResponse , role : 'assistant' } ,
318320 ] ) ;
@@ -335,7 +337,7 @@ describe('MongoDB Assistant', function () {
335337 await sendMessage ( testMessage ) ;
336338 await sendMessage ( testMessage ) ;
337339
338- expect ( await getDisplayedMessages ( browser ) ) . to . deep . equal ( [
340+ await waitForMessages ( browser , [
339341 { text : testMessage , role : 'user' } ,
340342 { text : testResponse , role : 'assistant' } ,
341343 { text : testMessage , role : 'user' } ,
@@ -344,7 +346,7 @@ describe('MongoDB Assistant', function () {
344346
345347 await clearChat ( browser ) ;
346348
347- expect ( await getDisplayedMessages ( browser ) ) . to . deep . equal ( [ ] ) ;
349+ await waitForMessages ( browser , [ ] ) ;
348350 } ) ;
349351 } ) ;
350352
@@ -363,7 +365,7 @@ describe('MongoDB Assistant', function () {
363365 } ,
364366 } ) ;
365367
366- expect ( await getDisplayedMessages ( browser ) ) . to . deep . equal ( [
368+ await waitForMessages ( browser , [
367369 { text : testMessage , role : 'user' } ,
368370 { text : testResponse , role : 'assistant' } ,
369371 { text : 'This is a different message' , role : 'user' } ,
@@ -372,28 +374,37 @@ describe('MongoDB Assistant', function () {
372374 } ) ;
373375
374376 it ( 'can copy assistant message to clipboard' , async function ( ) {
375- skipForWeb (
376- this ,
377- 'Accessing the clipboard text is not available in compass-web so this test is not meaningful'
378- ) ;
377+ if ( context . disableClipboardUsage ) {
378+ this . skip ( ) ;
379+ }
380+
379381 await sendMessage ( testMessage ) ;
380382
383+ // sanity check
384+ await browser . waitUntil ( async ( ) => {
385+ const messages = await getDisplayedMessages ( browser ) ;
386+ return messages [ 1 ] . text === testResponse ;
387+ } ) ;
388+
381389 const messageElements = await browser
382390 . $$ ( Selectors . AssistantChatMessage )
383391 . getElements ( ) ;
384392
385393 const assistantMessage = messageElements [ 1 ] ;
386394
387- const copyButton = assistantMessage . $ ( '[aria-label="Copy message"]' ) ;
388- await copyButton . waitForDisplayed ( ) ;
389- await copyButton . click ( ) ;
395+ await browser . clickVisible (
396+ assistantMessage . $ ( '[aria-label="Copy message"]' )
397+ ) ;
390398
391399 await browser . waitUntil ( async ( ) => {
392- return (
393- ( await browser . execute ( ( ) => {
394- return navigator . clipboard . readText ( ) ;
395- } ) ) === testResponse
396- ) ;
400+ const text = await clipboard . read ( ) ;
401+
402+ const isValid = text === testResponse ;
403+ if ( ! isValid ) {
404+ console . log ( text ) ;
405+ }
406+
407+ return isValid ;
397408 } ) ;
398409 } ) ;
399410
@@ -443,15 +454,9 @@ describe('MongoDB Assistant', function () {
443454 it ( 'opens assistant with explain plan prompt when clicking "Interpret for me"' , async function ( ) {
444455 await useExplainPlanEntryPoint ( browser ) ;
445456
446- const confirmButton = browser . $ ( 'button*=Confirm' ) ;
447- await confirmButton . waitForDisplayed ( ) ;
448- await confirmButton . click ( ) ;
457+ await browser . clickVisible ( 'button*=Confirm' ) ;
449458
450- await browser . waitUntil ( async ( ) => {
451- return ( await getDisplayedMessages ( browser ) ) . length === 2 ;
452- } ) ;
453-
454- expect ( await getDisplayedMessages ( browser ) ) . deep . equal ( [
459+ await waitForMessages ( browser , [
455460 {
456461 text : 'Interpret this explain plan output for me.' ,
457462 role : 'user' ,
@@ -472,9 +477,7 @@ describe('MongoDB Assistant', function () {
472477 ) ;
473478
474479 // Click Cancel button
475- const cancelButton = browser . $ ( 'button*=Cancel' ) ;
476- await cancelButton . waitForDisplayed ( ) ;
477- await cancelButton . click ( ) ;
480+ await browser . clickVisible ( 'button*=Cancel' ) ;
478481
479482 // Wait a bit to ensure no request is sent
480483 await browser . pause ( 300 ) ;
@@ -485,6 +488,7 @@ describe('MongoDB Assistant', function () {
485488 expect ( await chatMessages . getText ( ) ) . to . include (
486489 'Please confirm your request'
487490 ) ;
491+
488492 expect ( await chatMessages . getText ( ) ) . to . include ( 'Request cancelled' ) ;
489493
490494 // Verify no assistant request was made
@@ -509,8 +513,7 @@ describe('MongoDB Assistant', function () {
509513 browser . $ ( Selectors . ConnectionToastErrorDebugButton )
510514 ) ;
511515
512- const messages = await getDisplayedMessages ( browser ) ;
513- expect ( messages ) . deep . equal ( [
516+ await waitForMessages ( browser , [
514517 {
515518 text : 'Diagnose why my Compass connection is failing and help me debug it.' ,
516519 role : 'user' ,
@@ -535,23 +538,24 @@ async function openAssistantDrawer(browser: CompassBrowser) {
535538async function clearChat ( browser : CompassBrowser ) {
536539 const clearChatButton = browser . $ ( Selectors . AssistantClearChatButton ) ;
537540 if ( await clearChatButton . isDisplayed ( ) ) {
538- await clearChatButton . click ( ) ;
539- const confirmButton = browser . $ (
541+ await browser . clickVisible ( clearChatButton ) ;
542+ await browser . clickVisible (
540543 Selectors . AssistantConfirmClearChatModalConfirmButton
541544 ) ;
542- await confirmButton . waitForClickable ( ) ;
543- await confirmButton . click ( ) ;
544545 }
545546}
546547
547- async function getDisplayedMessages ( browser : CompassBrowser ) {
548+ async function getDisplayedMessages (
549+ browser : CompassBrowser
550+ ) : Promise < Message [ ] > {
548551 await browser . $ ( Selectors . AssistantChatMessages ) . waitForDisplayed ( ) ;
549552
550553 const messageElements = await browser
551554 . $$ ( Selectors . AssistantChatMessage )
552555 . getElements ( ) ;
553556
554- const displayedMessages = [ ] ;
557+ const displayedMessages : Message [ ] = [ ] ;
558+
555559 for ( const messageElement of messageElements ) {
556560 const textElements = await messageElement . $$ ( 'p' ) . getElements ( ) ;
557561 const isAssistantMessage =
@@ -572,6 +576,32 @@ async function getDisplayedMessages(browser: CompassBrowser) {
572576 return displayedMessages ;
573577}
574578
579+ async function waitForMessages (
580+ browser : CompassBrowser ,
581+ expectedMessages : Message [ ]
582+ ) {
583+ let lastDisplayedMessages : Message [ ] = [ ] ;
584+
585+ try {
586+ await browser . waitUntil ( async ( ) => {
587+ // the text streams so the message may not be complete immediately
588+ try {
589+ lastDisplayedMessages = await getDisplayedMessages ( browser ) ;
590+ expect ( lastDisplayedMessages ) . deep . equal ( expectedMessages ) ;
591+ } catch {
592+ return false ;
593+ }
594+ return true ;
595+ } ) ;
596+ } catch {
597+ console . log (
598+ 'Last displayed messages:' ,
599+ JSON . stringify ( lastDisplayedMessages )
600+ ) ;
601+ throw new Error ( 'Expected messages not found' ) ;
602+ }
603+ }
604+
575605async function useExplainPlanEntryPoint ( browser : CompassBrowser ) {
576606 await browser . clickVisible ( Selectors . AggregationExplainButton ) ;
577607
0 commit comments