forked from open-webui/open-webui
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add basic cypress test as initial work towards e2e tests
- Loading branch information
Showing
13 changed files
with
1,995 additions
and
38 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
name: Integration Test | ||
|
||
on: | ||
push: | ||
branches: | ||
- main | ||
- dev | ||
pull_request: | ||
branches: | ||
- main | ||
- dev | ||
|
||
jobs: | ||
cypress-run: | ||
name: Run Cypress Integration Tests | ||
runs-on: ubuntu-latest | ||
steps: | ||
- name: Checkout Repository | ||
uses: actions/checkout@v4 | ||
|
||
- name: Build and run Compose Stack | ||
run: | | ||
docker compose up --detach --build | ||
- name: Preload Ollama model | ||
run: | | ||
docker exec ollama ollama pull qwen:0.5b-chat-v1.5-q2_K | ||
- name: Cypress run | ||
uses: cypress-io/github-action@v6 | ||
with: | ||
browser: chrome | ||
wait-on: 'http://localhost:3000' | ||
config: baseUrl=http://localhost:3000 | ||
|
||
- uses: actions/upload-artifact@v4 | ||
if: always() | ||
name: Upload Cypress videos | ||
with: | ||
name: cypress-videos | ||
path: cypress/videos | ||
if-no-files-found: ignore | ||
|
||
- name: Extract Compose logs | ||
if: always() | ||
run: | | ||
docker compose logs > compose-logs.txt | ||
- uses: actions/upload-artifact@v4 | ||
if: always() | ||
name: Upload Compose logs | ||
with: | ||
name: compose-logs | ||
path: compose-logs.txt | ||
if-no-files-found: ignore |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import { defineConfig } from 'cypress'; | ||
|
||
export default defineConfig({ | ||
e2e: { | ||
baseUrl: 'http://localhost:8080' | ||
}, | ||
video: true | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
// eslint-disable-next-line @typescript-eslint/triple-slash-reference | ||
/// <reference path="../support/index.d.ts" /> | ||
|
||
// These tests run through the chat flow. | ||
describe('Settings', () => { | ||
// Wait for 2 seconds after all tests to fix an issue with Cypress's video recording missing the last few frames | ||
after(() => { | ||
// eslint-disable-next-line cypress/no-unnecessary-waiting | ||
cy.wait(2000); | ||
}); | ||
|
||
beforeEach(() => { | ||
// Login as the admin user | ||
cy.loginAdmin(); | ||
// Visit the home page | ||
cy.visit('/'); | ||
}); | ||
|
||
context('Ollama', () => { | ||
it('user can select a model', () => { | ||
// Click on the model selector | ||
cy.get('button[aria-label="Select a model"]').click(); | ||
// Select the first model | ||
cy.get('div[role="option"][data-value]').first().click(); | ||
}); | ||
|
||
it('user can perform text chat', () => { | ||
// Click on the model selector | ||
cy.get('button[aria-label="Select a model"]').click(); | ||
// Select the first model | ||
cy.get('div[role="option"][data-value]').first().click(); | ||
// Type a message | ||
cy.get('#chat-textarea').type('Hi, what can you do? A single sentence only please.', { | ||
force: true | ||
}); | ||
// Send the message | ||
cy.get('button[type="submit"]').click(); | ||
// User's message should be visible | ||
cy.get('.chat-user').should('exist'); | ||
// Wait for the response | ||
cy.get('.chat-assistant', { timeout: 120_000 }) // .chat-assistant is created after the first token is received | ||
.find('div[aria-label="Generation Info"]', { timeout: 120_000 }) // Generation Info is created after the stop token is received | ||
.should('exist'); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
// eslint-disable-next-line @typescript-eslint/triple-slash-reference | ||
/// <reference path="../support/index.d.ts" /> | ||
import { adminUser } from '../support/e2e'; | ||
|
||
// These tests assume the following defaults: | ||
// 1. No users exist in the database or that the test admin user is an admin | ||
// 2. Language is set to English | ||
// 3. The default role for new users is 'pending' | ||
describe('Registration and Login', () => { | ||
// Wait for 2 seconds after all tests to fix an issue with Cypress's video recording missing the last few frames | ||
after(() => { | ||
// eslint-disable-next-line cypress/no-unnecessary-waiting | ||
cy.wait(2000); | ||
}); | ||
|
||
beforeEach(() => { | ||
cy.visit('/'); | ||
}); | ||
|
||
it('should register a new user as pending', () => { | ||
const userName = `Test User - ${Date.now()}`; | ||
const userEmail = `cypress-${Date.now()}@example.com`; | ||
// Toggle from sign in to sign up | ||
cy.contains('Sign up').click(); | ||
// Fill out the form | ||
cy.get('input[autocomplete="name"]').type(userName); | ||
cy.get('input[autocomplete="email"]').type(userEmail); | ||
cy.get('input[type="password"]').type('password'); | ||
// Submit the form | ||
cy.get('button[type="submit"]').click(); | ||
// Wait until the user is redirected to the home page | ||
cy.contains(userName); | ||
// Expect the user to be pending | ||
cy.contains('Check Again'); | ||
}); | ||
|
||
it('can login with the admin user', () => { | ||
// Fill out the form | ||
cy.get('input[autocomplete="email"]').type(adminUser.email); | ||
cy.get('input[type="password"]').type(adminUser.password); | ||
// Submit the form | ||
cy.get('button[type="submit"]').click(); | ||
// Wait until the user is redirected to the home page | ||
cy.contains(adminUser.name); | ||
// Dismiss the changelog dialog if it is visible | ||
cy.getAllLocalStorage().then((ls) => { | ||
if (!ls['version']) { | ||
cy.get('button').contains("Okay, Let's Go!").click(); | ||
} | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
// eslint-disable-next-line @typescript-eslint/triple-slash-reference | ||
/// <reference path="../support/index.d.ts" /> | ||
import { adminUser } from '../support/e2e'; | ||
|
||
// These tests run through the various settings pages, ensuring that the user can interact with them as expected | ||
describe('Settings', () => { | ||
// Wait for 2 seconds after all tests to fix an issue with Cypress's video recording missing the last few frames | ||
after(() => { | ||
// eslint-disable-next-line cypress/no-unnecessary-waiting | ||
cy.wait(2000); | ||
}); | ||
|
||
beforeEach(() => { | ||
// Login as the admin user | ||
cy.loginAdmin(); | ||
// Visit the home page | ||
cy.visit('/'); | ||
// Open the sidebar if it is not already open | ||
cy.get('[aria-label="Open sidebar"]').then(() => { | ||
cy.get('button[id="sidebar-toggle-button"]').click(); | ||
}); | ||
// Click on the profile link | ||
cy.get('button').contains(adminUser.name).click(); | ||
// Click on the settings link | ||
cy.get('button').contains('Settings').click(); | ||
}); | ||
|
||
context('General', () => { | ||
it('user can open the General modal and hit save', () => { | ||
cy.get('button').contains('General').click(); | ||
cy.get('button').contains('Save').click(); | ||
}); | ||
}); | ||
|
||
context('Connections', () => { | ||
it('user can open the Connections modal and hit save', () => { | ||
cy.get('button').contains('Connections').click(); | ||
cy.get('button').contains('Save').click(); | ||
}); | ||
}); | ||
|
||
context('Models', () => { | ||
it('user can open the Models modal', () => { | ||
cy.get('button').contains('Models').click(); | ||
}); | ||
}); | ||
|
||
context('Interface', () => { | ||
it('user can open the Interface modal and hit save', () => { | ||
cy.get('button').contains('Interface').click(); | ||
cy.get('button').contains('Save').click(); | ||
}); | ||
}); | ||
|
||
context('Audio', () => { | ||
it('user can open the Audio modal and hit save', () => { | ||
cy.get('button').contains('Audio').click(); | ||
cy.get('button').contains('Save').click(); | ||
}); | ||
}); | ||
|
||
context('Images', () => { | ||
it('user can open the Images modal and hit save', () => { | ||
cy.get('button').contains('Images').click(); | ||
// Currently fails because the backend requires a valid URL | ||
// cy.get('button').contains('Save').click(); | ||
}); | ||
}); | ||
|
||
context('Chats', () => { | ||
it('user can open the Chats modal', () => { | ||
cy.get('button').contains('Chats').click(); | ||
}); | ||
}); | ||
|
||
context('Account', () => { | ||
it('user can open the Account modal and hit save', () => { | ||
cy.get('button').contains('Account').click(); | ||
cy.get('button').contains('Save').click(); | ||
}); | ||
}); | ||
|
||
context('About', () => { | ||
it('user can open the About modal', () => { | ||
cy.get('button').contains('About').click(); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
/// <reference types="cypress" /> | ||
|
||
export const adminUser = { | ||
name: 'Admin User', | ||
email: '[email protected]', | ||
password: 'password' | ||
}; | ||
|
||
const login = (email: string, password: string) => { | ||
return cy.session( | ||
email, | ||
() => { | ||
// Visit auth page | ||
cy.visit('/auth'); | ||
// Fill out the form | ||
cy.get('input[autocomplete="email"]').type(email); | ||
cy.get('input[type="password"]').type(password); | ||
// Submit the form | ||
cy.get('button[type="submit"]').click(); | ||
// Wait until the user is redirected to the home page | ||
cy.get('#chat-search').should('exist'); | ||
// Get the current version to skip the changelog dialog | ||
if (localStorage.getItem('version') === null) { | ||
cy.get('button').contains("Okay, Let's Go!").click(); | ||
} | ||
}, | ||
{ | ||
validate: () => { | ||
cy.request({ | ||
method: 'GET', | ||
url: '/api/v1/auths/', | ||
headers: { | ||
Authorization: 'Bearer ' + localStorage.getItem('token') | ||
} | ||
}); | ||
} | ||
} | ||
); | ||
}; | ||
|
||
const register = (name: string, email: string, password: string) => { | ||
return cy | ||
.request({ | ||
method: 'POST', | ||
url: '/api/v1/auths/signup', | ||
body: { | ||
name: name, | ||
email: email, | ||
password: password | ||
}, | ||
failOnStatusCode: false | ||
}) | ||
.then((response) => { | ||
expect(response.status).to.be.oneOf([200, 400]); | ||
}); | ||
}; | ||
|
||
const registerAdmin = () => { | ||
return register(adminUser.name, adminUser.email, adminUser.password); | ||
}; | ||
|
||
const loginAdmin = () => { | ||
return login(adminUser.email, adminUser.password); | ||
}; | ||
|
||
Cypress.Commands.add('login', (email, password) => login(email, password)); | ||
Cypress.Commands.add('register', (name, email, password) => register(name, email, password)); | ||
Cypress.Commands.add('registerAdmin', () => registerAdmin()); | ||
Cypress.Commands.add('loginAdmin', () => loginAdmin()); | ||
|
||
before(() => { | ||
cy.registerAdmin(); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
// load the global Cypress types | ||
/// <reference types="cypress" /> | ||
|
||
declare namespace Cypress { | ||
interface Chainable { | ||
login(email: string, password: string): Chainable<Element>; | ||
register(name: string, email: string, password: string): Chainable<Element>; | ||
registerAdmin(): Chainable<Element>; | ||
loginAdmin(): Chainable<Element>; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"extends": "../tsconfig.json", | ||
"compilerOptions": { | ||
"inlineSourceMap": true, | ||
"sourceMap": false | ||
} | ||
} |
Oops, something went wrong.