Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
5e2adb4
Simplify client: remove Svelte, use pure Astro with SSR data fetching
GeekTrainer Mar 1, 2026
7cfff68
Initial plan
Copilot Mar 1, 2026
5006a9b
Create mock API server, update e2e tests for SSR, remove unused esbui…
Copilot Mar 1, 2026
9a5fed4
Update workshop content to reflect pure Astro app (remove Svelte refe…
Copilot Mar 1, 2026
28e1ba4
Replace mock API with real Flask server using seeded test database fo…
Copilot Mar 1, 2026
a2e5bb1
Move DATABASE_PATH resolution into seed_test_database() function
Copilot Mar 1, 2026
dd43328
Remove Google Fonts, use system font stack instead
Copilot Mar 1, 2026
7b19f3b
Disable Astro telemetry via ASTRO_TELEMETRY_DISABLED env var in config
Copilot Mar 1, 2026
74bc88a
Merge pull request #178 from github-samples/copilot/simplify-client-app
GeekTrainer Mar 2, 2026
ab3e09f
Fix content alignment with simplified app
GeekTrainer Mar 2, 2026
6f610c0
Restructure project into app/ directory
GeekTrainer Mar 2, 2026
1d58a16
Add pagination to dogs API and homepage
GeekTrainer Mar 2, 2026
f2d26f5
Fix Vite serving errors for symlinked node_modules
GeekTrainer Mar 2, 2026
d84b9ae
Remove old client/, server/, scripts/ directories
GeekTrainer Mar 2, 2026
4d9a59c
Add dev tooling for Copilot sandbox environment
GeekTrainer Mar 2, 2026
042df81
Genericize sandbox script with external config
GeekTrainer Mar 3, 2026
1ec9322
Update content/1-hour/README.md
GeekTrainer Mar 9, 2026
314144e
Update PLAN.md
GeekTrainer Mar 9, 2026
f83547f
Initial plan
Copilot Mar 9, 2026
b091dc6
Merge main, resolve conflicts, fix content path references
Copilot Mar 9, 2026
d11f050
Reset actions-workshop content to main, remove .dev/ and PLAN.md
GeekTrainer Mar 9, 2026
b278230
Fix GitHub Actions workshop content issues
GeekTrainer Mar 10, 2026
b56c68a
Fix 1-hour and full-day workshop content issues
GeekTrainer Mar 10, 2026
decf271
Merge pull request #190 from github-samples/copilot/sub-pr-179
GeekTrainer Apr 13, 2026
368d84f
Update npm dependencies to latest major versions
GeekTrainer Apr 13, 2026
f304509
Remove adoption-site duplicate and .github/skills, gitignore .playwri…
GeekTrainer Apr 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
version: 2
updates:
- package-ecosystem: npm
directory: /client
directory: /app/client
schedule:
interval: weekly
groups:
Expand Down
11 changes: 9 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,12 @@ flask_session/
htmlcov/

# playwright
client/test-results/
client/playwright-report/
app/client/test-results/
app/client/playwright-report/
.playwright-mcp/

# e2e test database
app/server/e2e_test_dogshelter.db

# azure
.azure
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
# Pets workshop

This repository contains the project for two guided workshops to explore various GitHub features. The project is a website for a fictional dog shelter, with a [Flask](https://flask.palletsprojects.com/en/stable/) backend using [SQLAlchemy](https://www.sqlalchemy.org/) and [Astro](https://astro.build/) frontend using [Svelte](https://svelte.dev/) for dynamic pages.
This repository contains the project for three guided workshops to explore various GitHub features. The project is a website for a fictional dog shelter, with a [Flask](https://flask.palletsprojects.com/en/stable/) backend using [SQLAlchemy](https://www.sqlalchemy.org/) and an [Astro](https://astro.build/) frontend using [Tailwind CSS](https://tailwindcss.com/).

The available workshops are:

- **[One hour](./content/1-hour/README.md)** — focused on GitHub Copilot
- **[Full-day](./content/full-day/README.md)** — a full day-in-the-life of a developer using GitHub for their DevOps processes
- **[GitHub Actions](./content/github-actions/README.md)** — CI/CD pipelines from running tests to deploying to Azure

## Getting started

Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
31 changes: 31 additions & 0 deletions app/client/astro.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// @ts-check
process.env.ASTRO_TELEMETRY_DISABLED = '1';
import { defineConfig } from 'astro/config';
import tailwindcss from '@tailwindcss/vite';
import node from '@astrojs/node';
import fs from 'node:fs';
import path from 'node:path';

// Allow Vite to serve files from the real node_modules path when it is symlinked
const nodeModulesPath = path.resolve('node_modules');
const realNodeModulesPath = fs.realpathSync(nodeModulesPath);
const fsAllow = ['..'];
if (realNodeModulesPath !== nodeModulesPath) {
fsAllow.push(path.dirname(realNodeModulesPath));
}

// https://astro.build/config
export default defineConfig({
output: 'server',
vite: {
plugins: [tailwindcss()],
server: {
fs: {
allow: fsAllow,
},
},
},
adapter: node({
mode: 'standalone'
}),
});
28 changes: 22 additions & 6 deletions client/e2e-tests/README.md → app/client/e2e-tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ Make sure you have installed dependencies:
npm install
```

You also need Python 3 with Flask dependencies installed:
```bash
pip install -r ../server/requirements.txt
```

### Running Tests

```bash
Expand All @@ -34,33 +39,44 @@ npm run test:e2e:headed
npm run test:e2e:debug
```

## Test Architecture

Tests run against the real Flask server with a separate test database seeded with deterministic data. When Playwright starts, it:

1. Seeds a test database (`e2e_test_dogshelter.db` in the server directory) with known dogs and breeds
2. Starts the Flask server using the test database
3. Starts the Astro dev server pointing at the Flask server
4. Runs all e2e tests against the live application

The test data is defined in `../server/utils/seed_test_database.py`.

## Test Coverage

The tests cover the following core functionality:

### Homepage Tests
- Page loads with correct title and content
- Dog list displays properly
- Loading states work correctly
- Error handling for API failures

### About Page Tests
- About page content displays correctly
- Navigation back to homepage works

### Dog Details Tests
- Navigation from homepage to dog details
- Full dog details display correctly
- Navigation back from dog details to homepage
- Handling of invalid dog IDs

### API Integration Tests
- Successful API responses
- Empty dog list handling
- Network error handling
- Dogs render correctly on the homepage
- Dog details render correctly
- 404 handling for non-existent dogs
- Navigation from card to detail page

## Configuration

Tests are configured in `../playwright.config.ts` and automatically start the application servers using the existing `scripts/start-app.sh` script before running tests.
Tests are configured in `../playwright.config.ts` and automatically start the Flask and Astro servers before running tests.

The tests run against:
- Client (Astro): http://localhost:4321
Expand Down
File renamed without changes.
47 changes: 47 additions & 0 deletions app/client/e2e-tests/api-integration.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { test, expect } from '@playwright/test';

test.describe('API Integration', () => {
test('should render dogs from the API on the homepage', async ({ page }) => {
await page.goto('/');

const dogCards = page.getByTestId('dog-card');
await expect(dogCards).toHaveCount(6);

await expect(page.getByTestId('dog-name').nth(0)).toHaveText('Buddy');
await expect(page.getByTestId('dog-breed').nth(0)).toHaveText('Golden Retriever');

await expect(page.getByTestId('dog-name').nth(1)).toHaveText('Luna');
await expect(page.getByTestId('dog-breed').nth(1)).toHaveText('Husky');

await expect(page.getByTestId('dog-name').nth(2)).toHaveText('Max');
await expect(page.getByTestId('dog-breed').nth(2)).toHaveText('German Shepherd');
});

test('should render dog details from the API', async ({ page }) => {
await page.goto('/dog/1');

await expect(page.getByTestId('dog-details')).toBeVisible();
await expect(page.getByTestId('dog-name')).toHaveText('Buddy');
await expect(page.getByTestId('dog-breed')).toContainText('Golden Retriever');
await expect(page.getByTestId('dog-age')).toContainText('3');
await expect(page.getByTestId('dog-gender')).toContainText('Male');
await expect(page.getByTestId('dog-status')).toHaveText('Available');
});

test('should return 404 details for non-existent dog', async ({ page }) => {
await page.goto('/dog/99999');

await expect(page.getByTestId('error-message')).toBeVisible();
await expect(page.getByTestId('error-message')).toContainText('not found');
});

test('should link from dog card to detail page', async ({ page }) => {
await page.goto('/');

const firstCard = page.getByTestId('dog-card').first();
await firstCard.click();

await expect(page).toHaveURL(/\/dog\/1$/);
await expect(page.getByTestId('dog-details')).toBeVisible();
});
});
46 changes: 46 additions & 0 deletions app/client/e2e-tests/dog-details.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { test, expect } from '@playwright/test';

test.describe('Dog Details', () => {
test('should navigate to dog details from homepage', async ({ page }) => {
await page.goto('/');

const firstDogCard = page.getByTestId('dog-card').first();
const dogName = await page.getByTestId('dog-name').first().textContent();

await firstDogCard.click();

await expect(page).toHaveURL(/\/dog\/\d+/);
await expect(page).toHaveTitle(/Dog Details - Tailspin Shelter/);
await expect(page.getByTestId('dog-details')).toBeVisible();
await expect(page.getByTestId('dog-name')).toHaveText(dogName!);
});

test('should display full dog details for Buddy', async ({ page }) => {
await page.goto('/dog/1');

await expect(page.getByTestId('dog-details')).toBeVisible();
await expect(page.getByTestId('dog-name')).toHaveText('Buddy');
await expect(page.getByTestId('dog-breed')).toContainText('Golden Retriever');
await expect(page.getByTestId('dog-age')).toContainText('3');
await expect(page.getByTestId('dog-gender')).toContainText('Male');
await expect(page.getByTestId('dog-status')).toHaveText('Available');
await expect(page.getByTestId('dog-description')).toContainText('friendly and loyal');
});

test('should navigate back to homepage from dog details', async ({ page }) => {
await page.goto('/dog/1');

await page.getByTestId('back-link').click();

await expect(page).toHaveURL('/');
await expect(page.getByRole('heading', { name: 'Welcome to Tailspin Shelter' })).toBeVisible();
});

test('should handle invalid dog ID gracefully', async ({ page }) => {
await page.goto('/dog/99999');

await expect(page).toHaveTitle(/Dog Details - Tailspin Shelter/);
await expect(page.getByTestId('error-message')).toBeVisible();
await expect(page.getByTestId('back-link')).toBeVisible();
});
});
58 changes: 58 additions & 0 deletions app/client/e2e-tests/homepage.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { test, expect } from '@playwright/test';

test.describe('Tailspin Shelter Homepage', () => {
test('should load homepage and display title', async ({ page }) => {
await page.goto('/');

await expect(page).toHaveTitle(/Tailspin Shelter - Find Your Forever Friend/);

await expect(page.getByRole('heading', { name: 'Welcome to Tailspin Shelter' })).toBeVisible();

await expect(page.getByText('Find your perfect companion from our wonderful selection')).toBeVisible();
});

test('should display dog list', async ({ page }) => {
await page.goto('/');

await expect(page.getByRole('heading', { name: 'Available Dogs' })).toBeVisible();

const dogList = page.getByTestId('dog-list');
await expect(dogList).toBeVisible();

const dogCards = page.getByTestId('dog-card');
await expect(dogCards).toHaveCount(6);
});

test('should display dog names and breeds', async ({ page }) => {
await page.goto('/');

await expect(page.getByTestId('dog-name').nth(0)).toHaveText('Buddy');
await expect(page.getByTestId('dog-breed').nth(0)).toHaveText('Golden Retriever');

await expect(page.getByTestId('dog-name').nth(1)).toHaveText('Luna');
await expect(page.getByTestId('dog-breed').nth(1)).toHaveText('Husky');

await expect(page.getByTestId('dog-name').nth(2)).toHaveText('Max');
await expect(page.getByTestId('dog-breed').nth(2)).toHaveText('German Shepherd');
});

test('should display pagination controls', async ({ page }) => {
await page.goto('/');

const pagination = page.getByTestId('pagination');
await expect(pagination).toBeVisible();
await expect(page.getByTestId('pagination-info')).toContainText('Page 1 of 2');
await expect(page.getByTestId('pagination-next')).toBeVisible();
});

test('should navigate to page 2', async ({ page }) => {
await page.goto('/');

await page.getByTestId('pagination-next').click();
await expect(page).toHaveURL(/page=2/);

const dogCards = page.getByTestId('dog-card');
await expect(dogCards).toHaveCount(4);
await expect(page.getByTestId('dog-name').nth(0)).toHaveText('Rocky');
});
});
Loading
Loading