Polish README and point Network tab at GitHub
CI / skip-ci-check (pull_request) Successful in 38s
CI / secret-scan (pull_request) Successful in 28s
CI / node-ci (pull_request) Successful in 1m46s

This commit is contained in:
Builder
2026-08-05 14:59:19 -04:00
parent bc3124a426
commit 5e98765033
4 changed files with 30 additions and 67 deletions
+9 -37
View File
@@ -1,42 +1,18 @@
# portfolio.spec.ts
> A personal portfolio + resume styled as a **Playwright test runner**. Built by an SDET, for SDETs.
>
> Gitea remote: `ilia/sdetProfile` (local folder is usually cloned as `portfolio`).
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
[![Live](https://img.shields.io/badge/live-sdet.levkin.ca-0a0)](https://sdet.levkin.ca)
Click the green ▶ next to any test to "run" it — each passing test reveals a portfolio section. Filter by `@tag` chips like a real `--grep`. Includes a career-timeline trace viewer, a Source tab that renders the portfolio as actual-looking Playwright spec code, a **Network** tab that lists public [git.levkin.ca](https://git.levkin.ca/explore/repos) repos as Playwright-style `GET … 200` rows with expandable JSON (descriptions from the Gitea API or each repo's README), and a downloadable PDF resume.
Career portfolio styled as a **Playwright test runner** — sidebar explorer, editor tabs, trace viewer, Network tab, keyboard shortcuts.
**Live preview:** deploy locally (see below) or open `index.html` directly.
---
**Live:** [sdet.levkin.ca](https://sdet.levkin.ca) · **Public mirror:** [github.com/Gitilia/sdetProfile](https://github.com/Gitilia/sdetProfile)
## Why this exists
A traditional portfolio doesn't tell a hiring manager you live in test runners all day. This one does — every interaction is a love letter to the tooling SDETs use:
- Sidebar **Test Explorer** (400px) with collapsible suites, green run arrows, and tree-view ellipsis for long describe labels
- **Editor tab strip** (28px, slim) above the report — switch between `portfolio.spec.ts`, `projects.spec.ts`, `skills.spec.ts`, `playground.spec.ts`; each rescopes the explorer, report, source view, and status counts (cookie-persisted)
- **Status pill** merged into the editor bar with live pass/fail/skip counts per active spec
- **Overflow menu** (⋯) on the right of the editor tabs with `--workers=` and `--headed` controls
- **Skipped test** (`should meet performance budget`) with amber ⊘ icon — a 100% green suite looks fake; mixed outcomes feel authentic
- Progress bars under each test that fill in real time
- **Auto-run** first test on page load so the hero arrives with body content rendered (runner animation still plays)
- **Summary stripe** above the results list: `Last run · 1.4s · 4 passed · 1 skipped`
- **Pending preview** — the first idle test's body is shown expanded with a faded look + "click ▶ to run" overlay
- **Tag bar** with 6 visible tags + "+N more" expand chip + a `× clear` button
- **Count bubbles** right-aligned with consistent width, tabular numerals, outlined — reads as "metric" not "tag"
- VS Code dark+ / Playwright trace viewer palette (Inter + JetBrains Mono)
- A `Trace` tab that draws your career as a Gantt-style waterfall
- A `Source` tab that renders the active spec as a real-looking `.spec.ts` file
- A `Console` tab that logs test run events live
- A `Network` tab modeled on Playwright's network panel — one row per public Gitea repo (`GET https://git.levkin.ca/api/v1/repos/…`), click to expand the faux JSON body
- `--headed` toggle that slows animations down for demo mode
- `--workers=` selector for parallel test execution (1, 2, or 4 workers)
- Cookie-persisted theme cycle: dark → light → high-contrast (WCAG AAA)
- Keyboard shortcuts (`?` to see all): `R` run all, `X` reset, `T` theme, `/` grep, `1–9` run Nth test
- Mobile drawer for the test explorer, fully responsive
---
Hiring managers who work with SDETs already live in test runners. This site uses
that UI metaphor instead of a generic landing page: sidebar explorer, editor tabs,
status pill, trace viewer, network panel, and keyboard shortcuts modeled on
Playwright and VS Code.
## Stack
@@ -308,8 +284,4 @@ See [IDEAS.md](IDEAS.md) for the backlog. Quick wins on top, ambitious experimen
## License
MIT. Fork it, restyle it, replace the content with your own. If you ship a variation, a wave hello on LinkedIn would be appreciated but is not required.
---
**Built by** [Ilia Dobkin](https://www.linkedin.com/in/ilia-dobkin-8263343/) · Senior SDET · Remote (ET)
MIT — see [LICENSE](LICENSE). Fork and replace the content as you like.
+2 -2
View File
@@ -186,11 +186,11 @@
<div class="source" id="source"></div>
</div>
<!-- Network (Gitea repos as Playwright-style requests) -->
<!-- Network (public GitHub repos as Playwright-style requests) -->
<div class="pane" id="pane-network" role="tabpanel">
<div class="network-pane">
<div class="network-head">
<div class="network-head__title">Network · git.levkin.ca</div>
<div class="network-head__title">Network · github.com/Gitilia</div>
<div class="network-head__meta"><span class="legend"><i class="sw sw--pass"></i> 200</span><span class="legend mono">application/json</span></div>
</div>
<div class="network-scroll" id="gitea-network" data-repo-list="1"></div>
+1 -1
View File
@@ -677,7 +677,7 @@
}
const GF = window.PORTFOLIO;
const repos = GF && Array.isArray(GF.giteaRepos) ? GF.giteaRepos : [];
const apiRoot = 'https://git.levkin.ca/api/v1/repos/';
const apiRoot = 'https://api.github.com/repos/';
if (repos.length === 0) {
wrap.innerHTML = `<div class="network-empty">No <span class="mono">giteaRepos</span> in <span class="mono">js/data.js</span>. Hard refresh the page (<span class="mono">Cmd+Shift+R</span>). If deploying, redeploy after adding the array.</div>`;
return;
+18 -27
View File
@@ -8,7 +8,8 @@ window.PORTFOLIO = {
workAuth: "Canadian citizen",
email: "idobkin@gmail.com",
linkedin: "https://www.linkedin.com/in/ilia-dobkin-8263343/",
gitea: "https://git.levkin.ca",
gitea: "https://github.com/Gitilia",
github: "https://github.com/Gitilia",
site: "https://sdet.levkin.ca",
blurb:
"Senior SDET with 20+ years in audit/financial software and regulated web, including real-money iGaming. Deep across Playwright, Swagger/OpenAPI contract testing, and performance baselines integrated into CI/CD. I treat automation as a personal discipline as much as a job: scripts, shortcuts, and agents that streamline my day so engineering effort goes where it matters. Strong instinct for stabilizing flaky suites, tightening quality gates, and removing manual regression effort wherever it lives.",
@@ -185,7 +186,7 @@ window.PORTFOLIO = {
tags: ["@api","@perf"],
duration: 0,
steps: [
{ kind: "ok", title: 'navigate to Gitea API endpoint', dur: 45 },
{ kind: "ok", title: 'navigate to GitHub API endpoint', dur: 45 },
{ kind: "ok", title: 'send GET /api/v1/repos/ilia/portfolio', dur: 62 },
{ kind: "ok", title: 'assert status 200', dur: 8 },
{ kind: "fail", title: 'expect(latency).toBeLessThan(200)', dur: 347 },
@@ -377,30 +378,20 @@ window.PORTFOLIO = {
],
/**
* Public repos on git.levkin.ca — descriptions from Gitea API when set,
* otherwise first paragraph of README (see scripts/fetch-gitea-repos.mjs).
* API lists all 19 repos on one page (explore UI may paginate).
* Public repos on github.com/Gitilia — mirrored from Gitea for recruiters.
* Keep this list to public GitHub only (no private forge links).
*/
giteaRepos: [
{ full_name: "ilia/ansible", name: "ansible", html_url: "https://git.levkin.ca/ilia/ansible", language: "Makefile", description: "Ansible automation for development machines, service hosts, and Proxmox-managed guests (LXC-first, with a path for KVM VMs)." },
{ full_name: "ilia/AtAnyRate", name: "AtAnyRate", html_url: "https://git.levkin.ca/ilia/AtAnyRate", language: "Python", description: "Local Python application that identifies upcoming Toronto events likely to increase Airbnb demand, sends Telegram alerts, and optionally adjusts nightly prices via Playwright automation." },
{ full_name: "ilia/atlas", name: "atlas", html_url: "https://git.levkin.ca/ilia/atlas", language: "Python", description: "Atlas is a local, privacy-focused home voice agent system — planning, architecture documentation, and kanban tickets for building the system." },
{ full_name: "ilia/crkl", name: "crkl", html_url: "https://git.levkin.ca/ilia/crkl", language: "Kotlin", description: "Privacy-first Android AI assistant — circle or touch any element on-screen; on-device AI transcribes, summarizes, explains, or drafts responses." },
{ full_name: "ilia/dotfiles", name: "dotfiles", html_url: "https://git.levkin.ca/ilia/dotfiles", language: "", description: "Dotfiles and shell configuration for dev machines." },
{ full_name: "ilia/hilitehero", name: "hilitehero", html_url: "https://git.levkin.ca/ilia/hilitehero", language: "Python", description: "Python tool for extracting highlighted text from PDFs with precise ordering and hyphenation handling." },
{ full_name: "ilia/invoice", name: "invoice", html_url: "https://git.levkin.ca/ilia/invoice", language: "JavaScript", description: "CLI for generating professional PDF invoices from JSON — interactive and non-interactive modes with preview-first workflow." },
{ full_name: "ilia/Jobber", name: "Jobber", html_url: "https://git.levkin.ca/ilia/Jobber", language: "TypeScript", description: "Self-hosted job search orchestration — discover roles, score fit, draft resumes and cover letters, export PDFs, track email; you submit applications yourself." },
{ full_name: "ilia/kanban", name: "kanban", html_url: "https://git.levkin.ca/ilia/kanban", language: "", description: "Kanban board project on self-hosted Gitea." },
{ full_name: "ilia/linkedout", name: "linkedout", html_url: "https://git.levkin.ca/ilia/linkedout", language: "JavaScript", description: "Job market intelligence platform with integrated AI-powered insights — modular architecture for extensibility." },
{ full_name: "ilia/llm_council", name: "llm_council", html_url: "https://git.levkin.ca/ilia/llm_council", language: "Python", description: "Local web UI like ChatGPT but sends each query to multiple LLMs — your \"LLM council\" votes with diverse models." },
{ full_name: "ilia/mirror_match", name: "mirror_match", html_url: "https://git.levkin.ca/ilia/mirror_match", language: "TypeScript", description: "Photo guessing game — upload photos, others guess who is in the picture for points. Next.js, PostgreSQL, NextAuth." },
{ full_name: "ilia/nanobot", name: "nanobot", html_url: "https://git.levkin.ca/ilia/nanobot", language: "Python", description: "Ultra-lightweight personal AI assistant (Python; published on PyPI as nanobot-ai)." },
{ full_name: "ilia/onboarding", name: "onboarding", html_url: "https://git.levkin.ca/ilia/onboarding", language: "Shell", description: "Developer environment setup — automates 60+ apps and tools plus Git and SSH configuration." },
{ full_name: "ilia/outreach", name: "outreach", html_url: "https://git.levkin.ca/ilia/outreach", language: "JavaScript", description: "Node.js email outreach for campaigns to law firms — templates, tracking, and tests." },
{ full_name: "ilia/POTE", name: "POTE", html_url: "https://git.levkin.ca/ilia/POTE", language: "Python", description: "Research-oriented tool for tracking and analyzing public stock trades by government officials." },
{ full_name: "ilia/profile", name: "profile", html_url: "https://git.levkin.ca/ilia/profile", language: "TypeScript", description: "Profile / personal site — TypeScript." },
{ full_name: "ilia/punimtag", name: "punimtag", html_url: "https://git.levkin.ca/ilia/punimtag", language: "TypeScript", description: "Modern photo management and facial recognition system." },
{ full_name: "ilia/resume", name: "resume", html_url: "https://git.levkin.ca/ilia/resume", language: "", description: "Résumé generator based on the best-resume-ever project." }
{ full_name: "Gitilia/playkit", name: "playkit", html_url: "https://github.com/Gitilia/playkit", language: "TypeScript", description: "Shared Playwright + API test helpers — UI actions, API client, timings, metrics." },
{ full_name: "Gitilia/context-extractor", name: "context-extractor", html_url: "https://github.com/Gitilia/context-extractor", language: "JavaScript", description: "Browser extension + Playwright package that dumps AI-ready page markdown, console, and network noise." },
{ full_name: "Gitilia/PaperPod", name: "PaperPod", html_url: "https://github.com/Gitilia/PaperPod", language: "Python", description: "Offline overhead video → named PDFs for Paperless (CV + OCR)." },
{ full_name: "Gitilia/SwipeAnything", name: "SwipeAnything", html_url: "https://github.com/Gitilia/SwipeAnything", language: "TypeScript", description: "Local-first swipe-to-triage UI with pluggable adapters." },
{ full_name: "Gitilia/maCopy", name: "maCopy", html_url: "https://github.com/Gitilia/maCopy", language: "TypeScript", description: "Native macOS menu-bar clipboard manager (Tauri + React)." },
{ full_name: "Gitilia/slack-sieve", name: "slack-sieve", html_url: "https://github.com/Gitilia/slack-sieve", language: "Python", description: "Sieve a Slack workspace export before Mattermost import." },
{ full_name: "Gitilia/sdetProfile", name: "sdetProfile", html_url: "https://github.com/Gitilia/sdetProfile", language: "JavaScript", description: "Career portfolio styled as a Playwright test runner." },
{ full_name: "Gitilia/portfolio", name: "portfolio", html_url: "https://github.com/Gitilia/portfolio", language: "JavaScript", description: "Personal hub for iliadobkin.com — projects, demos, consulting lanes." },
{ full_name: "Gitilia/levkin.ca", name: "levkin.ca", html_url: "https://github.com/Gitilia/levkin.ca", language: "HTML", description: "Levkin Inc. public homepage — static stack site." },
{ full_name: "Gitilia/mirror_match", name: "mirror_match", html_url: "https://github.com/Gitilia/mirror_match", language: "TypeScript", description: "Photo guessing game — upload photos, others guess who is in the picture for points." },
]
};
@@ -482,7 +473,7 @@ function renderProjects(){
${_tags(p.tags)}
</div>`).join('')
}</div>
<p class="projects-foot">Public code on <a href="https://git.levkin.ca/explore/repos" target="_blank" rel="noopener">git.levkin.ca</a> — open the <button type="button" class="tab-link" data-tab="network">Network</button> tab for an API-style request list.</p>
<p class="projects-foot">Public code on <a href="https://github.com/Gitilia" target="_blank" rel="noopener">github.com/Gitilia</a> — open the <button type="button" class="tab-link" data-tab="network">Network</button> tab for an API-style request list.</p>
</div>`;
}
@@ -537,7 +528,7 @@ function renderContact(){
return `<div class="block"><div class="contact-grid">
<div class="contact-cell"><label>email</label><a href="mailto:${p.email}">${p.email}</a></div>
<div class="contact-cell"><label>linkedin</label><a href="${p.linkedin}" target="_blank" rel="noopener">in/idobkin</a></div>
<div class="contact-cell"><label>gitea (self-hosted)</label><a href="${p.gitea}" target="_blank" rel="noopener">git.levkin.ca</a></div>
<div class="contact-cell"><label>github</label><a href="${p.github || p.gitea}" target="_blank" rel="noopener">github.com/Gitilia</a></div>
<div class="contact-cell"><label>site</label><a href="${p.site}" target="_blank" rel="noopener">sdet.levkin.ca</a></div>
<div class="contact-cell"><label>location</label>${_esc(p.location)}${p.workAuth ? ` · ${_esc(p.workAuth)}` : ''}</div>
</div></div>`;
@@ -552,7 +543,7 @@ function renderPerfBudget(){
function renderResponseTime(){
return `<div class="block">
<p>The Gitea API endpoint <code>GET /api/v1/repos/ilia/portfolio</code> exceeded the <strong>200 ms</strong> latency budget three times in a row.</p>
<p>The GitHub API endpoint <code>GET /repos/Gitilia/portfolio</code> exceeded the <strong>200 ms</strong> latency budget three times in a row.</p>
<h4>What happened</h4>
<ul>
<li>Initial request returned in <strong>347 ms</strong> — likely a cold-start on the VPS.</li>