chore: Switch e2e tests to Chrome for Testing via mise
Replace snap-installed Chromium/chromedriver with Chrome for Testing installed via npx @puppeteer/browsers. Binaries live in $MISE_DATA_DIR/chrome-for-testing/ with version-agnostic path lookup. Add install-e2e mise task and required apt packages to README and CI.
This commit is contained in:
parent
b1e0a41797
commit
74e3b600fc
5 changed files with 82 additions and 145 deletions
6
.github/workflows/push.yml
vendored
6
.github/workflows/push.yml
vendored
|
|
@ -67,10 +67,10 @@ jobs:
|
||||||
cargo-${{ runner.os }}-
|
cargo-${{ runner.os }}-
|
||||||
|
|
||||||
- name: Install system dependencies
|
- name: Install system dependencies
|
||||||
run: sudo apt-get update && sudo apt-get install -y clang lld libssl-dev pkg-config
|
run: sudo apt-get update && sudo apt-get install -y clang lld libssl-dev pkg-config libatk1.0-0 libatk-bridge2.0-0 libcups2 libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 libxrandr2 libgbm1 libpango-1.0-0 libcairo2 libasound2t64 libnss3 libxshmfence1
|
||||||
|
|
||||||
- name: Install Chrome for E2E
|
- name: Install Chrome for Testing
|
||||||
uses: browser-actions/setup-chrome@v1
|
run: mise run install-e2e
|
||||||
|
|
||||||
- name: Run E2E tests
|
- name: Run E2E tests
|
||||||
run: cargo test --features e2e --test e2e -- --show-output
|
run: cargo test --features e2e --test e2e -- --show-output
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,14 @@ flyctl = "latest"
|
||||||
SQLX_OFFLINE = "false"
|
SQLX_OFFLINE = "false"
|
||||||
|
|
||||||
[tasks]
|
[tasks]
|
||||||
install-deps = "sudo apt-get update && sudo apt-get install -y --no-install-recommends clang lld libssl-dev pkg-config sqlite3"
|
install-deps = "sudo apt-get update && sudo apt-get install -y --no-install-recommends clang lld libssl-dev pkg-config sqlite3 libatk1.0-0 libatk-bridge2.0-0 libcups2 libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 libxrandr2 libgbm1 libpango-1.0-0 libcairo2 libasound2t64 libnss3 libxshmfence1"
|
||||||
|
|
||||||
|
install-e2e = """
|
||||||
|
CHROME_DIR="${MISE_DATA_DIR:-$HOME/.local/share/mise}/chrome-for-testing"
|
||||||
|
mkdir -p "$CHROME_DIR"
|
||||||
|
npx --yes @puppeteer/browsers install chrome@stable --path "$CHROME_DIR"
|
||||||
|
npx --yes @puppeteer/browsers install chromedriver@stable --path "$CHROME_DIR"
|
||||||
|
"""
|
||||||
|
|
||||||
install-py-deps = "uv tool install djlint"
|
install-py-deps = "uv tool install djlint"
|
||||||
|
|
||||||
|
|
|
||||||
145
README.md
145
README.md
|
|
@ -1,151 +1,16 @@
|
||||||

|
|
||||||
|
|
||||||
**B{rew}log** is a self-hosted specialty coffee logging platform optimised for filter brewing
|
|
||||||
enthusiasts. B{rew}log can be used for tracking roasters, roasts, brews, cafes and brewing gear.
|
|
||||||
|
|
||||||
B{rew}log features an LLM-powered "Bag Scanning" feature, which enables it to automatically fill
|
|
||||||
roaster and coffee information using a photo of a bag. It also supports "check-ins" to log coffee
|
|
||||||
enjoyed in a cafe.
|
|
||||||
|
|
||||||
B{rew}log ships as a single Rust binary that serves a web UI, a REST API, and a CLI client. The
|
|
||||||
application uses SQLite as a backend, and will automatically create and migrate the database on
|
|
||||||
start-up.
|
|
||||||
|
|
||||||
> [!NOTE]
|
|
||||||
> This application was built almost entirely with Claude Code. I used this project as an
|
|
||||||
> excuse to explore the current state of the art in agentic coding tools. I've reviewed
|
|
||||||
> much of the code, and I'm largely responsible for the rules and the layout of the repository
|
|
||||||
> but nonetheless the vast majority of the code was written by Claude and Opus 4.6.
|
|
||||||
|
|
||||||
## Quick Start (Demo)
|
|
||||||
|
|
||||||
Before you start, you'll need to sign up for [Openrouter](https://openrouter.ai) and
|
|
||||||
[Foursquare Places](https://foursquare.com/products/places/) and get API keys for both.
|
|
||||||
|
|
||||||
Then create a `docker.env` file:
|
|
||||||
|
|
||||||
```env
|
|
||||||
# You'll need an API key from OpenRouter
|
|
||||||
BREWLOG_OPENROUTER_API_KEY=sk-or-...
|
|
||||||
# I've had good results with Gemini models, but you can try 'openrouter/free' to experiment
|
|
||||||
BREWLOG_OPENROUTER_MODEL=google/gemini-3-flash-preview
|
|
||||||
# FourSquare Places API key for location searching
|
|
||||||
BREWLOG_FOURSQUARE_API_KEY=fsq3...
|
|
||||||
```
|
|
||||||
|
|
||||||
You can see the full list of configuration options [below](#configuration). Once your `.env` file
|
|
||||||
is complete, start the container using the environment file
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Create a data directory to store the database
|
|
||||||
mkdir data
|
|
||||||
# Run the container
|
|
||||||
docker run \
|
|
||||||
--rm \
|
|
||||||
-p 3000 \
|
|
||||||
--env-file docker.env \
|
|
||||||
-v $PWD/data:/data \
|
|
||||||
ghcr.io/jnsgruk/brewlog:latest
|
|
||||||
```
|
|
||||||
|
|
||||||
On first start with an empty database the server prints a one-time registration URL:
|
|
||||||
|
|
||||||
```
|
|
||||||
No users found. Register the first user at:
|
|
||||||
http://localhost:3000/register/abc123...
|
|
||||||
This link expires in 1 hour.
|
|
||||||
```
|
|
||||||
|
|
||||||
Open that URL, choose a display name, and register a passkey. This creates an account and
|
|
||||||
signs in automatically.
|
|
||||||
|
|
||||||
### Install from Git
|
|
||||||
|
|
||||||
To build and install from source, you'll need a working Rust toolchain:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo install --locked --git https://github.com/jnsgruk/brewlog.git
|
|
||||||
```
|
|
||||||
|
|
||||||
Then create a `.env` file containing at least your OpenRouter and Foursquare API keys, and start
|
|
||||||
the server:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
brewlog serve
|
|
||||||
```
|
|
||||||
|
|
||||||
### CLI Authentication
|
|
||||||
|
|
||||||
To use the CLI or API for write operations, create a token via browser hand-off:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
brewlog token create --name "my-cli-token"
|
|
||||||
# Browser opens → authenticate with a passkey → token printed once
|
|
||||||
|
|
||||||
export BREWLOG_URL="http://localhost:3000"
|
|
||||||
export BREWLOG_TOKEN="<token from above>"
|
|
||||||
|
|
||||||
# Create data from the CLI
|
|
||||||
brewlog roaster add --name "Radical Roasters" --country "United Kingdom"
|
|
||||||
```
|
|
||||||
|
|
||||||
Run `brewlog --help` for the full command reference.
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
All settings are read from environment variables or CLI flags. A `.env` file in the working
|
|
||||||
directory is loaded automatically via [dotenvy](https://crates.io/crates/dotenvy).
|
|
||||||
|
|
||||||
### Server (`brewlog serve`)
|
|
||||||
|
|
||||||
| Variable | Purpose | Default |
|
|
||||||
| -------------------------- | ---------------------------------------------------------------------- | ----------------------- |
|
|
||||||
| `BREWLOG_RP_ID` | WebAuthn Relying Party ID (server domain) | `localhost` |
|
|
||||||
| `BREWLOG_RP_ORIGIN` | WebAuthn Relying Party origin (full URL) | `http://localhost:3000` |
|
|
||||||
| `BREWLOG_DATABASE_URL` | Database connection string | `sqlite://brewlog.db` |
|
|
||||||
| `BREWLOG_BIND_ADDRESS` | Server bind address | `127.0.0.1:3000` |
|
|
||||||
| `BREWLOG_INSECURE_COOKIES` | Disable the `Secure` cookie flag (auto-enabled for localhost defaults) | `false` |
|
|
||||||
| `RUST_LOG` | Log level filter | `info` |
|
|
||||||
| `RUST_LOG_FORMAT` | Set to `json` for structured log output | — |
|
|
||||||
|
|
||||||
### CLI Client
|
|
||||||
|
|
||||||
| Variable | Purpose | Default |
|
|
||||||
| --------------- | ------------------------------------- | ----------------------- |
|
|
||||||
| `BREWLOG_URL` | Server URL | `http://localhost:3000` |
|
|
||||||
| `BREWLOG_TOKEN` | API bearer token for write operations | — |
|
|
||||||
|
|
||||||
### Integrations
|
|
||||||
|
|
||||||
| Variable | Purpose | Default |
|
|
||||||
| ---------------------------- | --------------------------------------------------------------------------- | ----------------- |
|
|
||||||
| `BREWLOG_OPENROUTER_API_KEY` | [OpenRouter](https://openrouter.ai/) API key for AI extraction | **required** |
|
|
||||||
| `BREWLOG_OPENROUTER_MODEL` | LLM model for AI extraction | `openrouter/free` |
|
|
||||||
| `BREWLOG_FOURSQUARE_API_KEY` | [Foursquare](https://foursquare.com/) Places API key for nearby cafe search | **required** |
|
|
||||||
|
|
||||||
## Development Setup
|
## Development Setup
|
||||||
|
|
||||||
Install [mise](https://mise.jdx.dev/) then set up the development environment:
|
Install [mise](https://mise.jdx.dev/) then set up the development environment:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo apt update
|
sudo apt update
|
||||||
sudo apt install -y clang mold pkg-config libssl-dev
|
sudo apt install -y clang mold pkg-config libssl-dev \
|
||||||
|
libatk1.0-0 libatk-bridge2.0-0 libcups2 libdrm2 libxkbcommon0 \
|
||||||
|
libxcomposite1 libxdamage1 libxrandr2 libgbm1 libpango-1.0-0 \
|
||||||
|
libcairo2 libasound2t64 libnss3 libxshmfence1
|
||||||
mise trust
|
mise trust
|
||||||
mise install # Install all dev tools
|
mise install # Install all dev tools
|
||||||
uvx djlint --version # Install djlint via uv (for template formatting)
|
mise run install-e2e # Install Chrome for Testing + ChromeDriver
|
||||||
prek install # Install git hooks
|
|
||||||
```
|
|
||||||
|
|
||||||
## Development Setup
|
|
||||||
|
|
||||||
Install [mise](https://mise.jdx.dev/) then set up the development environment:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo apt update
|
|
||||||
sudo apt install -y clang mold pkg-config libssl-dev
|
|
||||||
mise trust
|
|
||||||
mise install # Install all dev tools
|
|
||||||
uvx djlint --version # Install djlint via uv (for template formatting)
|
|
||||||
prek install # Install git hooks
|
prek install # Install git hooks
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ impl BrowserSession {
|
||||||
caps.add_arg("--disable-gpu")?;
|
caps.add_arg("--disable-gpu")?;
|
||||||
caps.add_arg("--disable-dev-shm-usage")?;
|
caps.add_arg("--disable-dev-shm-usage")?;
|
||||||
caps.add_arg("--window-size=1280,1024")?;
|
caps.add_arg("--window-size=1280,1024")?;
|
||||||
|
caps.set_binary(&find_chrome_binary())?;
|
||||||
|
|
||||||
let driver = WebDriver::new(&chromedriver_url, caps).await?;
|
let driver = WebDriver::new(&chromedriver_url, caps).await?;
|
||||||
driver
|
driver
|
||||||
|
|
@ -40,3 +41,35 @@ impl BrowserSession {
|
||||||
let _ = self.driver.quit().await;
|
let _ = self.driver.quit().await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn find_chrome_binary() -> String {
|
||||||
|
find_chrome_for_testing_binary("chrome", "chrome-linux64/chrome")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_chrome_for_testing_binary(subdir: &str, binary: &str) -> String {
|
||||||
|
let base = std::env::var("CHROME_FOR_TESTING_DIR").unwrap_or_else(|_| {
|
||||||
|
format!(
|
||||||
|
"{}/chrome-for-testing",
|
||||||
|
std::env::var("MISE_DATA_DIR").unwrap_or_else(|_| {
|
||||||
|
format!(
|
||||||
|
"{}/.local/share/mise",
|
||||||
|
std::env::var("HOME").unwrap_or_default()
|
||||||
|
)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
let base_path = std::path::Path::new(&base).join(subdir);
|
||||||
|
if let Ok(entries) = std::fs::read_dir(&base_path) {
|
||||||
|
for entry in entries.flatten() {
|
||||||
|
let path = entry.path().join(binary);
|
||||||
|
if path.exists() {
|
||||||
|
return path.to_string_lossy().to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
panic!(
|
||||||
|
"Chrome for Testing binary not found at {base_path:?}. Run `mise run install-e2e` to install it."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ fn spawn_chromedriver(port: u16) -> Child {
|
||||||
let (tx, rx) = std::sync::mpsc::sync_channel(1);
|
let (tx, rx) = std::sync::mpsc::sync_channel(1);
|
||||||
|
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
let mut cmd = Command::new("chromedriver");
|
let mut cmd = Command::new(find_chromedriver_binary());
|
||||||
cmd.arg(format!("--port={port}"))
|
cmd.arg(format!("--port={port}"))
|
||||||
.stdout(Stdio::null())
|
.stdout(Stdio::null())
|
||||||
.stderr(Stdio::null());
|
.stderr(Stdio::null());
|
||||||
|
|
@ -110,3 +110,35 @@ pub fn ensure_chromedriver() -> u16 {
|
||||||
|
|
||||||
guard.as_ref().unwrap().port
|
guard.as_ref().unwrap().port
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn find_chromedriver_binary() -> String {
|
||||||
|
find_chrome_for_testing_binary("chromedriver", "chromedriver-linux64/chromedriver")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_chrome_for_testing_binary(subdir: &str, binary: &str) -> String {
|
||||||
|
let base = std::env::var("CHROME_FOR_TESTING_DIR").unwrap_or_else(|_| {
|
||||||
|
format!(
|
||||||
|
"{}/chrome-for-testing",
|
||||||
|
std::env::var("MISE_DATA_DIR").unwrap_or_else(|_| {
|
||||||
|
format!(
|
||||||
|
"{}/.local/share/mise",
|
||||||
|
std::env::var("HOME").unwrap_or_default()
|
||||||
|
)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
let base_path = std::path::Path::new(&base).join(subdir);
|
||||||
|
if let Ok(entries) = std::fs::read_dir(&base_path) {
|
||||||
|
for entry in entries.flatten() {
|
||||||
|
let path = entry.path().join(binary);
|
||||||
|
if path.exists() {
|
||||||
|
return path.to_string_lossy().to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
panic!(
|
||||||
|
"ChromeDriver binary not found at {base_path:?}. Run `mise run install-e2e` to install it."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue