Skip to content

Commit

Permalink
chore(ui) - fix playwright e2e tests (#713)
Browse files Browse the repository at this point in the history
* Fix file upload success indicator hiding

* Fix disable checkbox for file uploading

* Improve sorting logic and fix disable send (rate limiting logic)

* Create unique pdfs for file-management tests

* Refactor to handle LF API not allowing file read stream yet (for file uploads)

* Re-enable skipped tests that were flaky

* Add cleanup step at end of tests (reduces flakiness when test files were trying to clean up when other files were still running)

* Refactor to 1 worker and only chromium - running more than 1 worker can cause flakiness due to test files being run at the same time in different browsers (e.x. navigation history is incorrect). Additionally, Leapfrog API is slow when attaching files to assistants, resulting in flaky tests. We can attempt in increase number of browser and workers in the pipeline when the API is faster.
  • Loading branch information
andrewrisse authored Jul 10, 2024
1 parent 7d65636 commit 28c5941
Show file tree
Hide file tree
Showing 35 changed files with 1,003 additions and 578 deletions.
2 changes: 1 addition & 1 deletion src/leapfrogai_ui/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ OPENAI_API_KEY=
# SUPABASE AUTH (when running Supabase Locally)
SUPABASE_AUTH_KEYCLOAK_CLIENT_ID=uds-supabase
SUPABASE_AUTH_KEYCLOAK_SECRET=<secret>
SUPABASE_AUTH_EXTERNAL_KEYCLOAK_URL=https://keycloak.admin.uds.dev/realms/uds
SUPABASE_AUTH_EXTERNAL_KEYCLOAK_URL=https://sso.uds.dev/realms/uds

# PLAYWRIGHT
USERNAME=[email protected]
Expand Down
2 changes: 1 addition & 1 deletion src/leapfrogai_ui/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ Running Supabase locally:
```
SUPABASE_AUTH_KEYCLOAK_CLIENT_ID=uds-supabase
SUPABASE_AUTH_KEYCLOAK_SECRET=<secret> #this is the client secret for the client in Keycloak
SUPABASE_AUTH_EXTERNAL_KEYCLOAK_URL=https://keycloak.admin.uds.dev/realms/uds
SUPABASE_AUTH_EXTERNAL_KEYCLOAK_URL=https://sso.uds.dev/realms/uds
```

After it starts, the Supabase API URL and Anon key are printed to the console. These are used in the .env file to connect to Supabase.
Expand Down
43 changes: 43 additions & 0 deletions src/leapfrogai_ui/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/leapfrogai_ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
"jsdom": "^24.0.0",
"lodash": "^4.17.21",
"otpauth": "^9.2.2",
"pdf-lib": "^1.17.1",
"prettier": "^3.1.1",
"prettier-plugin-svelte": "^3.1.2",
"sass": "^1.71.1",
Expand Down
77 changes: 45 additions & 32 deletions src/leapfrogai_ui/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,57 @@
import { devices } from '@playwright/test';
import type { PlaywrightTestConfig } from '@playwright/test';
import { devices } from '@playwright/test';
import dotenv from 'dotenv';

dotenv.config();

const chromeConfig = {
name: 'chromium',
use: {
...devices['Desktop Chrome'],
// Use prepared auth state.
storageState: 'playwright/.auth/user.json'
},
dependencies: ['setup']
};

// LEAVE THIS COMMENTED OUT CODE FOR DEVELOPMENT PURPOSES/TESTING
// const firefoxConfig = {
// name: 'firefox',
// use: {
// ...devices['Desktop Firefox'],
// // Use prepared auth state.
// storageState: 'playwright/.auth/user.json'
// },
// dependencies: ['setup']
// };
// const webkitConfig = {
// name: 'webkit',
// use: { ...devices['Desktop Safari'], storageState: 'playwright/.auth/user.json' },
// dependencies: ['setup']
// };
// const edgeConfig = {
// name: 'Edge',
// use: {
// ...devices['Desktop Edge'],
// channel: 'msedge',
// storageState: 'playwright/.auth/user.json'
// },
// dependencies: ['setup']
// };

const config: PlaywrightTestConfig = {
// running more than 1 worker can cause flakiness due to test files being run at the same time in different browsers
// (e.x. navigation history is incorrect)
// Additionally, Leapfrog API is slow when attaching files to assistants, resulting in flaky tests
// We can attempt in increase number of browser and workers in the pipeline when the API is faster
workers: 1,
projects: [
{ name: 'setup', testMatch: /.*\.setup\.ts/ },
{ name: 'setup', testMatch: /global\.setup\.ts/, teardown: 'cleanup' },
{
name: 'chromium',
use: {
...devices['Desktop Chrome'],
// Use prepared auth state.
storageState: 'playwright/.auth/user.json'
},
dependencies: ['setup']
name: 'cleanup',
testMatch: /global\.teardown\.ts/
},
{
name: 'firefox',
use: {
...devices['Desktop Firefox'],
// Use prepared auth state.
storageState: 'playwright/.auth/user.json'
},
dependencies: ['setup']
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'], storageState: 'playwright/.auth/user.json' },
dependencies: ['setup']
},
{
name: 'Edge',
use: {
...devices['Desktop Edge'],
channel: 'msedge',
storageState: 'playwright/.auth/user.json'
},
dependencies: ['setup']
}
{ ...chromeConfig }
],
webServer: {
command: 'npm run build && npm run preview',
Expand Down
3 changes: 1 addition & 2 deletions src/leapfrogai_ui/src/lib/components/AssistantForm.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
} from '$lib/constants';
import { superForm } from 'sveltekit-superforms';
import { page } from '$app/stores';
import { beforeNavigate, goto, invalidate } from '$app/navigation';
import { beforeNavigate, goto } from '$app/navigation';
import { Button, Modal, Slider, TextArea, TextInput } from 'carbon-components-svelte';
import AssistantAvatar from '$components/AssistantAvatar.svelte';
import { yup } from 'sveltekit-superforms/adapters';
Expand All @@ -26,7 +26,6 @@
invalidateAll: false,
validators: yup(isEditMode ? editAssistantInputSchema : assistantInputSchema),
onResult({ result }) {
invalidate('lf:assistants');
if (result.type === 'redirect') {
toastStore.addToast({
kind: 'success',
Expand Down
40 changes: 40 additions & 0 deletions src/leapfrogai_ui/src/lib/components/AttachFile.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<script lang="ts">
import { Attachment } from 'carbon-icons-svelte';
import { Button } from 'carbon-components-svelte';
export let disabled = false;
export let accept = ['.pdf', '.txt', '.text'];
export let multiple = false;
export let files: File[] = [];
export let handleAttach: () => void;
</script>

<Button
icon={Attachment}
kind="ghost"
size="small"
iconDescription="Attach File"
on:click={handleAttach}
/>

<input
{disabled}
type="file"
tabindex="-1"
accept={accept.join(',')}
{multiple}
name="files"
class:bx--visually-hidden={true}
{...$$restProps}
on:change|stopPropagation={({ target }) => {
if (target) {
files = [...target.files];
}
}}
on:click
on:click={({ target }) => {
if (target) {
target.value = null;
}
}}
/>
9 changes: 8 additions & 1 deletion src/leapfrogai_ui/src/lib/helpers/chatHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,13 @@ export const sortMessages = (messages: VercelOrOpenAIMessage[]): VercelOrOpenAIM
const timeA = normalizeTimestamp(a);
const timeB = normalizeTimestamp(b);

return timeA - timeB;
if (timeA === timeB) {
if (a.role === b.role) {
return 0; // same created_at and role
}
return a.role === 'user' ? -1 : 1; // user comes before assistant
}
return timeA - timeB; // sort by created_at
});
};

Expand Down Expand Up @@ -88,6 +94,7 @@ export const stopThenSave = async ({
title: 'Response Canceled',
subtitle: 'Response generation canceled.'
});
threadsStore.setSendingBlocked(false);
};

export const getAssistantImage = (assistants: LFAssistant[], assistant_id: string) => {
Expand Down
2 changes: 1 addition & 1 deletion src/leapfrogai_ui/src/lib/helpers/fileHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ export const convertFileObjectToFileRows = (files: FileObject[]): FileRow[] =>
id: file.id,
filename: file.filename,
created_at: file.created_at,
status: 'complete'
status: 'hide'
}));
2 changes: 1 addition & 1 deletion src/leapfrogai_ui/src/lib/mocks/supabase-mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const sessionMock = vi.fn(() => {
email,
email_verified: true,
full_name,
iss: 'https://keycloak.admin.uds.dev/realms/uds',
iss: process.env.SUPABASE_AUTH_EXTERNAL_KEYCLOAK_URL,
name: full_name,
phone_verified: false,
provider_id: faker.string.uuid(),
Expand Down
20 changes: 15 additions & 5 deletions src/leapfrogai_ui/src/lib/stores/filesStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ const createFilesStore = () => {
// Remove the error files after 1.5 seconds
new Promise((resolve) => setTimeout(resolve, 1500)).then(() => {
update((old) => {
old.files.forEach((row) => (row.status = 'hide'));
return {
...old,
pendingUploads: [...old.pendingUploads.filter((row) => row.status !== 'error')]
Expand All @@ -115,16 +116,25 @@ const createFilesStore = () => {
},
setAllUploadingToError: () => {
update((old) => {
const modifiedFiles = old.files.map((file) => {
if (file.status === 'uploading') {
file.status = 'error';
}
const modifiedFiles = old.pendingUploads.map((file) => {
file.status = 'error';

return file;
});

// Remove the error files after 1.5 seconds
new Promise((resolve) => setTimeout(resolve, 1500)).then(() => {
update((old) => {
return {
...old,
pendingUploads: []
};
});
});

return {
...old,
files: modifiedFiles,
pendingUploads: modifiedFiles,
uploading: false
};
});
Expand Down
11 changes: 9 additions & 2 deletions src/leapfrogai_ui/src/lib/stores/threads.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,15 @@ const createThreadsStore = () => {
return { ...old, selectedAssistantId };
});
},
setSendingBlocked: (status: boolean) => {
update((old) => ({ ...old, sendingBlocked: status }));
// Important - this method has a built in delay to ensure next user message has a different timestamp when setting to false (unblocking)
setSendingBlocked: async (status: boolean) => {
if (!status && process.env.NODE_ENV !== 'test') {
new Promise((resolve) => setTimeout(resolve, 1000)).then(() => {
update((old) => ({ ...old, sendingBlocked: status }));
});
} else {
update((old) => ({ ...old, sendingBlocked: status }));
}
},
changeThread: async (newId: string | null) => {
await goto(`/chat/${newId}`);
Expand Down
Loading

0 comments on commit 28c5941

Please sign in to comment.