Skip to content

Commit

Permalink
Refactored GH Action, added playwright test
Browse files Browse the repository at this point in the history
  • Loading branch information
vanpelt committed Jun 21, 2024
1 parent 689c57d commit d7302e7
Show file tree
Hide file tree
Showing 15 changed files with 578 additions and 982 deletions.
81 changes: 78 additions & 3 deletions .github/workflows/docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,11 @@ env:
IMAGE_NAME: ${{ github.repository }}

jobs:
build-and-push-image:
build-frontend:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
attestations: write
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
Expand Down Expand Up @@ -51,6 +49,28 @@ jobs:
- name: Build frontend
working-directory: ./frontend
run: npm run build
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: frontend-${{ github.sha }}
path: ./frontend/dist

build-and-push-image:
needs: build-frontend
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
attestations: write
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
name: frontend-${{ github.sha }}
path: ./backend/openui/dist
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
Expand All @@ -66,6 +86,11 @@ jobs:
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=tag
type=ref,event=pr
type=sha
- name: Build and push Docker image
id: push
uses: docker/build-push-action@v6
Expand All @@ -83,3 +108,53 @@ jobs:
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME}}
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true

test:
needs: build-and-push-image
timeout-minutes: 10
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
version: 9
run_install: false
- name: Install Node.js
uses: actions/setup-node@v4
with:
cache-dependency-path: frontend/pnpm-lock.yaml
node-version: 20
cache: "pnpm"
- name: Get pnpm store directory
shell: bash
working-directory: ./frontend
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- uses: actions/cache@v4
name: Setup pnpm cache
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Install dependencies
working-directory: ./frontend
run: pnpm install
- name: Install Playwright Browsers
working-directory: ./frontend
run: pnpm exec playwright install --with-deps
- name: Get short SHA
id: get_short_sha
run: echo "::set-output name=short_sha::$(git rev-parse --short HEAD)"
- name: Run Playwright tests
env:
DOCKER_TAG: sha-${{ steps.get_short_sha.outputs.short_sha }}
working-directory: ./frontend
run: pnpm exec playwright test
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30
85 changes: 85 additions & 0 deletions backend/openui/dummy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
from openai.types.chat import ChatCompletionChunk
import random
import time
import asyncio
import uuid
from .logs import logger

GOOD_DUMMY_RESPONSE = """---
name: Dummy
emoji: 🤖
---
<div class="text-foreground">
<h1 class="text-primary">Hello, world!</h1>
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
<p>This is a dummy response.</p>
<div class="w-full bg-secondary text-secondary-foreground">
<img src="https://placehold.co/600x400" alt="Placeholder Image">
</div>
<div class="prose">
<h2>Some more text</h2>
<p>This is some more text.</p>
<button class="bg-primary text-primary-foreground">Click me</button>
</div>
</div>
"""


def content_to_openai(content, id):
data = {
"id": str(id),
"object": "chat.completion.chunk",
"created": int(time.time()),
"model": "openui-dummy",
"system_fingerprint": None,
"choices": [
{
"index": 0,
"delta": {"content": content},
"role": "assistant",
"logprobs": None,
"finish_reason": None,
}
],
}
return ChatCompletionChunk.model_validate(data)


def ollama_chunk_to_sse(content, id):
data = content_to_openai(content, id)
return f"data: {data.model_dump_json()}\n\n"


async def dummy_stream_generator(input):
id = uuid.uuid1()
if input.get("model") == "dummy/good":
response = GOOD_DUMMY_RESPONSE
else:
response = "This is a bad dummy response."
response_length = len(response)
chunk_size = 10
start = 0
while start < response_length:
end = min(start + random.randint(1, chunk_size), response_length)
chunk = response[start:end]
start = end
yield ollama_chunk_to_sse(chunk, id)
await asyncio.sleep(random.uniform(0.01, 0.10))
yield "data: [DONE]\n\n"


class DummyStreamGenerator:
def __init__(self, *args, **kwargs):
self.generator_func = dummy_stream_generator
self.args = args
self.kwargs = kwargs

async def __aiter__(self):
generator = self.generator_func(*self.args, **self.kwargs)
async for item in generator:
yield item
5 changes: 5 additions & 0 deletions backend/openui/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
from .models import count_tokens, ShareRequest, VoteRequest
from .ollama import ollama_stream_generator, openai_to_ollama
from .openai import openai_stream_generator
from .dummy import DummyStreamGenerator
from .db.models import User, Usage, Vote, Component
from .util import storage
from .util import get_git_user_email
Expand Down Expand Up @@ -200,6 +201,10 @@ def gen():
return openai_stream_generator(response, input_tokens, user_id, 0)

return StreamingResponse(gen(), media_type="text/event-stream")
elif data.get("model").startswith("dummy"):
return StreamingResponse(
DummyStreamGenerator(data), media_type="text/event-stream"
)
raise HTTPException(status=404, detail="Invalid model")
except (ResponseError, APIStatusError) as e:
traceback.print_exc()
Expand Down
3 changes: 1 addition & 2 deletions frontend/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@
"error",
{
"devDependencies": [
"cypress.config.ts",
"vite.config.ts",
"src/setupTests.ts",
"src/testUtils.tsx",
Expand Down Expand Up @@ -184,7 +183,7 @@
}
},
{
"files": ["vite.config.ts", "cypress.config.ts"],
"files": ["vite.config.ts", "playwright.config.ts"],
"parserOptions": {
"project": ["./tsconfig.node.json"]
}
Expand Down
6 changes: 5 additions & 1 deletion frontend/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,8 @@ dist-ssr
coverage
.nyc_output
.stylelintcache
cypress/videos
cypress/videos
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
28 changes: 28 additions & 0 deletions frontend/integration_tests/basic.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { expect, test } from '@playwright/test'
import { HOST } from './util'

test('test good flow', async ({ page }) => {
await page.goto(`${HOST}/ai/new?dummy=good`)

await expect(page).toHaveTitle(/Create a new UI/)
await page.locator('#llm-input button[type="submit"]').click()
// Wait for our LLM to finish generating the UI
await page.waitForFunction(
s => !document.querySelector(s),
'#llm-input .rendering',
{ timeout: 10000 }
)
await page
.getByRole('button', { name: 'Toggle dark/light mode', exact: true })
.click()
await page.getByRole('button', { name: 'Change theme', exact: true }).click()
await page.getByRole('button', { name: 'Orange' }).click()
const iframe = await page.frameLocator('#version-0')
await expect(iframe.locator('h1')).toHaveText('Hello, world!')
await expect(iframe.locator('img')).toHaveAttribute(
'src',
/.*unsplash\.com.*/
)
const annotator = await page.$('#version-0')
await annotator?.screenshot({ path: 'annotator-screenshot.png' })
})
1 change: 1 addition & 0 deletions frontend/integration_tests/util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const HOST = 'http://127.0.0.1:7979'
Loading

0 comments on commit d7302e7

Please sign in to comment.