• Home
  • Engineering Outcomes
  • AI Assisted Rest API
  • Portfolio Website
  • Web Automation
  • HTTP Emulators
  • VM Cluster Deployment
  • Test Insights
Aleksandr Polskiy

Aleksandr Polskiy

Staff / Principal Software QA Engineer - Test Infrastructure & Automation Architecture

Architecting scalable test frameworks, custom service emulators, and CI/CD validation pipelines across enterprise platforms requiring 99.999% SLA reliability - owning the automation infrastructure, the release gates that depend on it, and the engineering standards that keep both trustworthy.

GitHub Profile • LinkedIn Profile • • Location: San Mateo / San Francisco Bay Area, CA

Enterprise and Cloud Test Frameworks, Quality Infrastructure Testing and Engineering

With over 15 years of technical experience in test framework architecture, system qualification, and quality engineering leadership, I specialize in building robust test automation tools, enterprise pipelines, and environment emulators. My approach combines full web UI and API automation with machine learning test optimization, deep log analytics, and strict Requirement Traceability Matrix (RTM) governance to guarantee release quality.

At staff and principal scope this means designing test infrastructure that stays economical across hundreds of builds, defining the release gates that decide whether a build ships, driving shift-left adoption through cross-functional technical review, and mentoring engineers on the standards that make automation worth trusting. Every project linked on this site is publicly readable and continuously verified - the pipelines, the quality gates, and the failure diagnostics are all inspectable rather than asserted.

Technical Skills Matrix

Area Core Technologies & Methodologies
Strategic QA Leadership and Engineering Governance Automation & Test Infrastructure Architecture, Release Gate Qualification, Test Determinism & Flake Elimination, Shift-Left Strategy, Cross-Functional Technical Reviews, Team Mentorship, Quality Strategy, System Qualification, Release Planning, Requirement Traceability Matrix (RTM), TestIt, PlanIt, Jira
Automation & Programming Python (advanced automation & scripting), Custom Python Frameworks, Playwright, Pytest, Selenium WebDriver, REST API Validation, Fault Injection & Service Virtualization, Custom Service Emulators (Flask, http.server), Process-Level Test Isolation, Static Analysis Gating (Pylint), Go (Golang), JavaScript (ES6+), HTML5, CSS3, Shell/Bash Scripting, PowerShell Automation, Strict Static Typing (mypy)
Backend Verification & Databases PostgreSQL, MSSQL, Oracle SQL, MySQL, Apache Cassandra, ElasticSearch, Data Integrity Testing, Backend Automation
Networking & Security Infrastructure TCP/IP, UDP, IPsec, DNS, TLS/SSL, SSH/SFTP, SIP, HTTP/S, HTTP Load Balancing & Active Health Checking (Apache mod_proxy_balancer), Authenticated Encryption (AES-256-GCM), Ed25519 Key Management, Credential-Free Image Hardening, Wireshark (deep-packet analysis), cURL utilities
Infrastructure & DevOps Docker, Docker Compose, Jenkins Agents, GitHub Actions, Cross-Platform CI Matrices (Linux/Windows, multi-version Python), Infrastructure Provisioning & Configuration Management, Golden-Image Pipelines, cloud-init First-Boot Provisioning, OCI Registries (GHCR, Docker Hub, ORAS), High-Availability & Failover Testing, Git, AWS (EC2), VMware, VirtualBox, VM Deployments
Operating Systems & Environments Linux (RHEL, Ubuntu, CentOS, Oracle Linux), Windows, macOS
Intelligent Tooling & Data Science scikit-learn (test prioritization & flaky-test detection), Pandas, NumPy, custom log-parsing utilities, LLM-assisted debugging, agentic workflows, prompt engineering, Grafana telemetry

Selected Engineering Outcomes

Every outcome below is verifiable in source rather than self-reported - the Evidence column opens the project it belongs to.

Measured Impact
Zero flaky failures across the pipeline's entire 27-run history. All eight failures resolved to a named cause: five to one real mobile-layout defect the suite caught on its first run at a 390px viewport, three to the same cross-repository ordering race, where a site and its test framework were deployed out of step. None was retried away, and the eight runs since are green.
Diagnosis moved off the developer's machine. Each failure ships a screenshot, the rendered DOM, the browser's own error log, a replayable trace, and an LLM root-cause verdict, so a red build is read rather than reproduced - all three infrastructure failures above were diagnosed from the CI log alone, which named the colliding elements outright.

Full write-up: Case Study - Making a CI Signal Trustworthy.

Outcome Evidence
Eliminated a class of false CI failures. Gated the suite on deployment settlement - no in-flight Pages run, then a repeated ETag - instead of trusting an HTTP 200. A half-propagated CDN had been timing out locators that were never broken. Web Automation
Cut runner spend without cutting coverage. Cancel-on-supersede concurrency collapses a commit burst into roughly one suite execution. The policy is deliberately inverted where an external API quota, not compute, is the scarce resource - there runs are serialized instead. The same question asked one level up - which commits deserve a run at all - found a pipeline spending capped monthly API quota on documentation-only commits, re-confirming a result that could not have moved; its trigger now excludes paths that cannot change the outcome. Web AutomationAI Assisted Rest API
Built self-extending suites. A crawler discovers the route surface at collection time; one entity dataset fans out across every suite that requests it. Publishing a page or adding an entity grows coverage with no test edit. Web AutomationAI Assisted Rest API
Reduced time-to-resolution on red builds. Each failure ships a screenshot, the rendered DOM, the browser's own error log, a replayable Playwright trace, and an LLM root-cause verdict - diagnosis happens from the CI artifact instead of a local reproduction. Web Automation
Closed the gap between a page that renders and a page that works. The browser's own error log - console, failed requests, error responses, unhandled exceptions - was recorded only by the per-route load checks and by nothing that clicks, so an exception thrown while a visitor used a tab was invisible to the entire suite: the assertions that followed just timed out on a locator and reported nothing about the cause. Every page now records, the interaction path is asserted, and the log is attached to every failure whether or not the failing test was looking at it. Scoping those assertions to the site's own origin also removed an unintended third-party dependency the deploy path had picked up through an embedded badge image. Web Automation
Made quality gates blocking, not advisory. Static analysis runs at a hard 10.00/10 threshold before any browser starts, so a regression fails the pipeline rather than accumulating in a log nobody reads. Web Automation
Found a quality gate that existed only on paper. A second framework declared the same standard and enforced none of it: its README required Pylint compliance, its machine-readable code rules required it again, and a .pylintrc sat in the repository root - but no pipeline step and no Make target ever invoked the linter, and the config file's own first line was a stray language label that left it unparseable, so even a manual run silently fell back to Pylint's defaults rather than the settings the file appeared to specify. Confirmed by probe rather than inferred. Measured for the first time, the code scored 9.45/10 against nineteen findings, among them a config file opened with no explicit encoding - a real defect on any non-UTF-8 console. It now runs at a blocking 10.00/10 ahead of the test job, which is also the cheaper order: the linter spends none of the capped API quota the suite spends. AI Assisted Rest API
Caught four repositories deleting their own test history, with three days to spare. Every suite was producing per-test results faithfully and every one was losing them on a rolling 90-day clock - and for public repositories 90 days is GitHub's retention maximum, not its default, so no configuration change could have saved them. Nothing had noticed because each run looked green in isolation; what was missing was the record across runs, which is the only thing that can say whether a failure is a flake or a regression. Measuring first showed the oldest artifacts expiring in three days, which set the build order: capture the data, leave everything reproducible for later. The backfill took 13,458 results across 234 tests and 136 runs, reaching back to 2026-05-21. Test Insights
Declined to publish a metric until it was calibrated against the real distribution. Asked to move from pass/fail assertions toward error budgets, I built one - and the first version was wrong in a way only the data could show. Holding each test to a multiple of its own median flagged 51 of 225 tests as having spent their budget, which would have read as a portfolio-wide performance problem. It was arithmetic on noise: the median test in this record has a 1 ms baseline and 145 of 234 sit under 10 ms, so a 3x tolerance was firing at 3 ms - scheduler jitter, not degradation. Adding a measured 100 ms floor reduced it to 78 tests judged and zero breaches, and the measurement that justifies the floor is recorded beside it rather than left as a chosen constant. The same review suggested p99 thresholds; the record does not support them, since the average test carries 24 to 154 observations and a p99 over 24 samples is the single worst run wearing a percentile's name. Test Insights
Disproved my own design assumption before writing the code that depended on it. PortfolioTestInsights, the cross-repository collector behind this tab, was specified around JUnit XML, which every suite was assumed to emit. Downloading one artifact per repository showed otherwise: the largest suite emits none at all, and a third publishes raw Allure results whose schema differs from the generated report in timing, step location, and the meaning of name. Adding --junitxml to each suite would have been a one-line change and would have recovered no historical run whatever, since the artifacts were already frozen. Three parsers shipped instead of one. The same survey caught an identity trap: one suite titles its tests, so keying on the displayed name would have forked a test's history every time someone improved a title - now prevented outright by assigned identifiers across all 233 tests in four repositories. Test Insights
Wired event-driven cross-repository regression. A front-end deploy dispatches an event into a separate automation repository, which waits for propagation and re-validates the live site unattended. Portfolio WebsiteWeb Automation
Found and fixed a real defect with the suite. A mobile overflow assertion caught a 398px document inside a 390px viewport on its first run, traced to two independent CSS causes - both since corrected and now regression-covered. Portfolio Website
Published reusable fault-injection infrastructure. A containerized HTTP error-code simulator plus a dependency-free emulator with server-lifecycle controls, letting suites reproduce specific 4xx/5xx conditions and hung-backend timeouts on demand. HTTP Emulators
Made self-terminating software deterministically testable. The fault-injection emulators had no tests, and the obvious reason is that they are hostile to testing: their control codes stall the process for two minutes, drop the listener, or exit non-zero mid-request. A shared server would have made every result depend on which destructive test ran first. Each test now owns a subprocess on an ephemeral port, readiness comes from polling the socket rather than sleeping, and the two-minute stall is asserted through a short client-side timeout instead of being waited out. That suite went green on its first run across Ubuntu and Windows against Python 3.12 and 3.14, and stands at 108 tests today, with a further 28 exercising the published container rather than the source. HTTP Emulators
Proved the isolation rather than assuming it. Collection order is randomised on every run and CI does a second pass under a fixed seed, so a suite that only passes in one order fails instead of passing by luck. Static analysis, gated at 10.00/10 ahead of the tests, also surfaced a latent defect in the emulator itself: a control path referenced a listener that could be unbound, raising a NameError instead of performing its documented restart. HTTP Emulators
Cut the published container 8.6x and proved the reduction holds. The image shipped on a full Debian base with the pip cache retained and an unrelated log file baked in, and its .dockerignore sat outside the build context, so Docker never read it - a rebuild from a working directory would have copied a 528 MB local virtual environment into a public image. A slim base plus ignore rules scoped to the context root took the published download from 397 MB to 46 MB. Confirmed by rebuilding from a deliberately dirtied context and asserting the artifact still held only the application and its seven-package dependency closure. HTTP Emulators
Closed the gap between what the page claims and what is actually true. Proving the image was minimal on the day it was built proves nothing about the day after, and a figure typed into prose stops being true the first time the thing it counts changes. Neither failure is visible from a browser - the tab still renders, the link still resolves, the number still looks like a number - so the page would go on advertising claims nothing had checked since the day they were written. The suite now reads the published image straight from the registry, resolving a pull token, the platform manifest and the layer metadata to confirm the installed dependency closure still matches; and it reads the suite-size figures back off the page to compare them against the suite that is running. Registry access is kept off the deployment path: a third party has no business gating a deploy signal. Web AutomationHTTP Emulators
Audited the audit, and found it was checking 2 links out of 16. A green link-integrity test proved nothing: role-based locators skip elements hidden from the accessibility tree, and in a tabbed layout every inactive panel is hidden, so only the header and footer were ever inspected. Caught by measuring coverage instead of trusting the passing result. The check now resolves all 16 targets, prefers HEAD with a GET fallback, paces requests per host after rate limiting was traced to the check itself, and runs on a schedule so no deploy waits on a third party. Web Automation
Cut a load-balancer outage from 2 lost requests in 10 to at most the one already in flight. The balancer discovered a dead backend only by failing live traffic at it, then waited out a 60-second retry timer before trying again, so an outage cost a stream of errors at both ends. Active health checking on a 2-second interval with a single-failure threshold replaced that. The result is measured rather than described: each backend was killed outright while the balancer was polled continuously, giving 114 requests with zero errors on one run and 95 with one on the other - the request already committed to the dying member, which took 5.0s, the balancer's own timeout expiring against a machine that no longer existed. So the published claim is not that failover is free, but that it costs at most that one request and nothing after it. VM Cluster Deployment
Made a VM image publishable without granting access to every cluster deployed from it. A usable image and a distributable one pull in opposite directions: whatever is baked in so the creator can log in also lets every downloader log in, permanently and to every guest cloned from it. The build now strips cached registry, git and shell credentials, every human login, and every SSH key including the operational account's own, then fails the export rather than shipping if one survived, a check worth having precisely because the failure would otherwise be silent and irreversible once published. Identity is supplied per guest instead, from a 66 KB first-boot seed carrying the deployer's own key, which also deleted a boot-rename-reboot pass from provisioning. Two people deploying the same image get clusters neither can reach in the other's environment. VM Cluster Deployment
Tested the two things the runner made it easy not to test. Hosted runners offer no nested virtualization, so the resilience behaviour could not execute in CI at all; and the image publish and pull scripts were fully documented but had never once been run, which is how a wrong default path survived in them. Both are the shape of gap that stays green forever. The scripts now make a real round trip through an OCI registry on every push, asserting the appliance comes back byte-identical, and the failover exercise runs against a container stack modelling the same topology. That substitution is only honest because the two targets are pinned to the same hostnames, the same scheduling, and the same health-check settings in both configurations - written to match and commented as such, because a drift between them would leave the CI result meaningless while still reporting green. VM Cluster Deployment
AI Assisted Rest API
Repo apolskiy/CountryWeather
Tagline Resilient, data-driven REST API test framework with response-time SLA gates and AI-assisted engineering conventions
Overview A Python REST API automation framework validating two independent public services - REST Countries v5 and Open-Meteo v1 - from one shared architecture. Entity data lives in a single source of truth (test_data/master_entities.json) that a pytest_generate_tests hook expands across both suites, while a centralized client layer absorbs the network reality of testing live third-party APIs: burst limits, transient gateway errors, and latency regressions.
Key Features & Highlights
  • Single Source of Truth & Dynamic Parametrization: Any test in any module that declares an entity parameter is automatically parametrized across every record in master_entities.json, with the entity name labelling each generated case - test_country_schema[Germany] rather than [entity0]. Five entities currently expand nine test functions into 33 executed cases; adding a country grows both suites with no test edit.
  • Case Labels Are Not Identity: That entity label reads well in a report and is the wrong thing to key history on, because it moves whenever the dataset is edited and says nothing about which test ran. Identity is carried separately by an assigned ID - CWA_10001 through CWA_10009, authored once as @pytest.mark.test_id and republished by a collection hook as both an Allure label and a JUnit property. A test can be renamed, re-parametrized or moved between modules and its history still stitches, which is what makes the cross-repository record on the Test Insights tab able to follow a test across renames at all.
  • Network Resilience: A centralized ApiClient retries connection and timeout failures plus HTTP 429/502/503/504 with exponential backoff (retry_backoff * 2**attempt), deferring to the server's Retry-After header when present.
  • Proactive Rate-Limit Pacing: Beyond reactive retries, the client enforces a minimum interval between the start of consecutive requests across the whole session, so the suite never bursts past the v5 API's short-window limit in the first place.
  • Performance SLA Gates: Every call is asserted against a per-environment max_response_time (5.0s countries, 3.0s weather). Only the successful attempt is timed - backoff and failed-attempt time are deliberately excluded, so a retry storm cannot be misread as server latency.
  • Typed Schema Validation: Responses are parsed into dataclass validators through explicit from_dict factories that check field presence and type before construction, normalizing the v5 object model so tests keep a stable shape. Presence-only assertions are rejected by convention.
  • Quota-Aware CI (Deliberately Inverted Concurrency): Runs are serialized account-wide under a branch-agnostic concurrency group with cancel-in-progress: false - the exact opposite of the cancel-on-supersede policy used where runner minutes are the scarce resource. Here the constraint is an external shared quota, not compute: a per-branch group would let two branches race the same rate limit, and cancelling an in-flight run would spend quota on a result nobody reads. The scarce resource dictates the policy.
  • Not Every Commit Deserves a Run: The same reasoning applied one level up, after a documentation-only commit was observed running the full suite against the live API to re-confirm a result that could not have moved. The workflow had triggered on every push to any branch; it now ignores Markdown, licence and editor-metadata paths. Deliberately a denylist rather than an allowlist - an allowlist has to be extended whenever a directory is added, and forgetting is silent, leaving new code that never runs in CI while the badge stays green. A denylist fails the other way, where the worst case is one unneeded run that costs quota once and is visible.
  • One Entry Point: A Makefile owns both the pytest and the pylint invocation along with the artifact layout, so make test and make lint execute the identical commands locally and in CI, emitting JUnit XML, a self-contained HTML report, and Allure results in one run.
  • Static Analysis Gates the Suite: A lint job runs pylint --fail-under=10 over every tracked .py file, and the test job will not start unless it passes. That ordering is the quota argument turned on itself: the linter needs no API key and spends nothing, so a failure there costs seconds, where running the suite first would spend capped monthly requests to learn what static analysis already knew. The file list is read from git ls-files rather than written into the recipe, so a new module is covered the moment it is tracked.
  • AI-Assisted Engineering: Framework conventions - externalized datasets, mandatory schema validation, typed signatures, Google-style docstrings - are codified as machine-readable rules in .claude/rules/, with .claude/skills/CLAUDE_LOG.md recording the architectural decisions and their rationale. Rules are written for a generator to follow; the lint gate above is what makes them binding, because a convention nothing checks is a preference.
Tech Stack
  • Core: Python 3.14, Pytest 9.0.2, Requests 2.32.5, REST Countries v5 & Open-Meteo v1
  • Architecture: pytest_generate_tests data-driven parametrization, YAML-configured environments (PyYAML 6.0.3) injected via fixtures, dataclass schema validators, bearer auth read from the environment
  • Reporting: Allure Pytest 2.16.0, Pytest HTML 4.2.0, JUnit XML
  • CI/CD: GitHub Actions on Ubuntu runners, Make-driven execution, Pylint 4.0.6 as a blocking 10.00/10 gate ahead of the suite, selectable suite via --env=countries|weather, Allure CLI report generation
CI / Build Status CountryWeather CI Pipeline status
Documentation CountryWeather README
Portfolio Website
Repo https://github.com/apolskiy/apolskiy.github.io
Tagline Responsive software QA portfolio & live target application for automated E2E testing
Overview A hand-written personal portfolio site showcasing frontend fundamentals and quality engineering work. Every page is authored in semantic HTML5, CSS3, and vanilla ES6+ JavaScript with no framework and no build step - the sources are served exactly as committed. Hosted on GitHub Pages, the site is both an interactive professional showcase and the live target application for the PlaywrightAPWebsiteAutomation end-to-end regression suite.
Key Features & Highlights
  • Zero-Dependency Frontend: No framework, bundler output, or third-party runtime - one stylesheet and one script drive the entire page.
  • Single-Page Tab Routing: Vanilla ES6+ DOM manipulation swaps data-tab panels in place across seven tabs, with no page reloads and no router library. A standalone case-study page sits alongside it as a real second document, reached by an ordinary relative link.
  • Cross-Tab Citations: Each engineering outcome cites the project it can be verified against, and the citation opens that project's tab rather than leaving the site. These controls carry a button role instead of being anchors, because every anchor here must resolve to an absolute target or another page of the site, and a bare fragment is neither. Keyboard activation is part of the contract, not an afterthought.
  • Anti-Spam Obfuscation, With Its Limits Stated: Every outbound URL and the contact address ship as Base64 payloads (data-h / data-e) and are decoded into real anchors on DOMContentLoaded, so a scraper that fetches the HTML without executing it finds no addresses; a <noscript> fallback keeps the site reachable without JavaScript. That is obfuscation rather than encryption, and claiming more would be dishonest: once the decoder runs the address is a working mailto: in the DOM, visible in the status bar on hover and in devtools, Base64 is an encoding a scraper can simply decode from the attribute, and any headless browser defeats the mechanism outright - as this site's own crawler demonstrates, since it renders each page in a real browser precisely because an HTTP-only crawler would find none of these links. Keeping the address in a real anchor is the deliberate trade: a portfolio exists to be contacted, and the alternative costs a reader the ability to copy it while stopping nobody who can run a browser.
  • Responsive Layout: A CSS Flexbox tab strip plus a max-width: 600px breakpoint that wraps the navigation and restructures the skills matrix for mobile viewports.
  • Continuously Verified: Each push runs the GitHub Pages build-and-deployment job, and the deployed page is then re-validated end to end by the Playwright suite in the Web Automation tab.
Tech Stack
  • Frontend: Semantic HTML5, CSS3 (Flexbox, media-query breakpoint), JavaScript (ES6+, DOM manipulation, Base64 decoding)
  • Hosting & Deployment: GitHub Pages (pages-build-deployment), Git
  • Quality: Validated in CI by the Playwright / Pytest E2E suite
CI / Build Status Portfolio Website GitHub Pages build and deployment status
Documentation apolskiy.github.io README
Web Automation
Repo https://github.com/apolskiy/PlaywrightAPWebsiteAutomation
Tagline Production-grade Playwright & Pytest E2E suite with dynamic site crawling, AI failure triage, and Allure reporting
Overview An E2E web automation and dynamic route-discovery framework built in Python with Playwright and Pytest. It runs functional, layout, and cross-viewport regressions against https://apolskiy.github.io/, generating its own route list at collection time so the suite grows with the site instead of being hand-maintained.
Key Features & Highlights
  • Page Object Model (POM): Strict encapsulation - test modules contain no selectors and never touch a Playwright Page directly, so a markup change is absorbed by the Page Object alone.
  • Dynamic Site Exploration: An async Playwright crawler renders each page in a real browser (the target publishes its links as Base64, so an HTTP-only crawler would discover nothing), walks the site breadth-first under depth and page-count limits, and writes reports/sitemap.json - the artifact that parameterizes a health check per discovered route.
  • AI-Powered Failure Triage: On an assertion failure the rendered DOM snapshot and the browser's own error log are sent to Claude for a structured root-cause verdict attached to the Allure report. The two answer different halves of the question - the DOM shows what the page ended up as, the log shows what went wrong on the way there - and a script that threw before it could bind the tab router explains a missing panel far more directly than the absence of that panel does. The prompt states how to read the log rather than leaving it to be inferred: a failure supported only by third-party events is a flake, and an empty log is evidence in its own right, since it rules out script exceptions and missing resources. Both inputs are size-bounded, and either one that gets shortened says so in the prompt - a model reasoning over a fragment it believes is complete gives a confident wrong answer rather than a missing one. The inspector is strictly advisory: a transport or credential problem degrades to "no report" rather than failing the run.
  • Cross-Viewport Coverage: Every layout rule is asserted on both sides of the site's 600px breakpoint - desktop 1920x1080 and mobile 390x844 - including sideways-overflow checks at each size.
  • Deployment Resilience: Before any browser context launches, CI polls the site repository's Actions API until no Pages deployment is in flight, then waits for the served ETag to repeat across consecutive polls - an HTTP 200 alone is no proof of freshness, and testing a moving CDN target is how a passing locator times out mid-run.
  • Concurrency Control & Runner-Cost Efficiency: Concurrency groups scoped by workflow, event, and ref with cancel-in-progress: true. During a rapid commit cycle each new push cancels the superseded run instead of queueing behind it, so a burst of N commits consumes roughly one full-suite execution rather than N - billed runner minutes track the number of states worth testing, not the number of pushes. The same rule removes the race where two runs assert against the same deployment slot.
  • Blocking Quality Gate: pylint --fail-under=10 across every package and conftest.py runs before the suite - a style regression fails the pipeline instead of printing a warning.
  • No Third Parties on the Deploy Path: The one check that resolves off-site links is marked external and deselected from the deployment run, then executed on a weekly schedule instead. Link rot is a monthly problem; checking it per push would put GitHub and Docker Hub in the critical path of a deploy signal and generate enough request pressure to trip rate limiting on its own. It fails only on 404/410, because 401, 403 and 429 mean "not reachable by an anonymous caller" rather than "gone".
  • Evidence-Integrity Checks: The suite holds the site to its own claims: every published outcome must cite a project, each citation must open the tab it names, and every project panel must carry a documentation link and a build badge sourced from that project's own repository. A stale reference cannot quietly point a reader somewhere wrong.
  • Published Artefact Verification: The claim that the emulator's public image carries Flask and nothing beyond its own dependency closure is checked against the image itself - anonymous pull token, platform manifest, then the layer blobs, reading installed distribution metadata to confirm the closure still matches what this site advertises. No container runtime is involved, and no HTTP client was added for it. A careless rebuild is invisible from the browser: the tab still renders, the link still resolves, and only the artefact changes - which is precisely why it is read rather than trusted.
  • Diagnosable Failures (Developer Experience): A red build ships everything needed to explain itself. Each failure attaches a full-page screenshot, the fully rendered DOM including script-injected nodes, the browser's own error log for that page, a Playwright trace openable in Trace Viewer for step-by-step time travel through the run, and the Claude verdict - so the first question a developer asks is answered from the CI artifact rather than by reproducing the failure locally. Reproduction is the expensive step in time-to-resolution; these artifacts are designed to remove it.
  • Dual Reporting Engine: Native Java 21 Allure HTML reporting paired with a self-contained Pytest HTML summary, both uploaded as build artifacts. Every test carries epic / feature / story / severity metadata and each phase is an Allure step, so the report reads as a user journey instead of a click log.
Tech Stack
  • Core Automation: Playwright 1.61.0, Python 3.10+ (developed and CI-pinned on 3.14), Pytest 9.1.1, pytest-playwright 0.8.0
  • AI Diagnostics: Anthropic SDK 0.120.2 (Claude-based failure triage)
  • Reporting & Imaging: Allure Pytest 2.16.0, Pytest HTML 4.2.0, Playwright traces, Pillow 12.3.0
  • Quality & Config: Pylint 4.0.6 held at 10.00/10, python-dotenv 1.2.2
  • CI/CD: GitHub Actions on Ubuntu (Temurin JDK 21, Allure CLI 2.29.0), two workflows - a deployment-gated E2E pipeline and a weekly outbound link check - plus Bash / cURL polling against the GitHub REST API
Test Coverage

84 tests on the deployment path, plus 2 that run weekly - 86 in total, all against the live site. Those 2 are the only tests that depend on anything outside this site being reachable - one resolves every outbound link the page publishes, the other reads the published container image from its registry - so they are deselected from the deploy run and execute on a schedule instead. A deploy signal has no business failing because GitHub or Docker Hub is briefly unavailable.

Both figures above are read back off this page on every run and compared against the suite that is executing, so neither can drift out of date unnoticed - they are verified rather than maintained.

Ten modules cover SPA routing, cross-viewport layout, base64 link decoding, shared hover styling, per-project panel completeness and internal consistency, engineering-outcome evidence integrity, published-container verification, the suite-size figures this site quotes, per-route health, and runtime health while the page is being used.

Every page records what the browser complains about - console errors, requests that failed, responses of 400 and above, and unhandled JavaScript exceptions - from before the first navigation until the page closes. On a failure that log is attached to both reports alongside the screenshot and trace, whether or not the failing test was looking at it. Two tests assert on it: one per discovered route at load, and one that walks all eight tabs first, because the router and the link decoder are the only scripts here and neither runs until something is clicked. Those assertions are scoped to this site's own origin - the CI badges above are served by GitHub, and a bad minute there is worth recording but not worth failing a deploy over.

The suite grows without being edited. 18 tests are generated at collection time by crawling the site's own route graph, so a newly published page arrives already checked for status, console and network errors, rendered content, a usable meta description, and layout at both viewports. Several other modules parameterize over the tab list itself, so publishing a project tab adds its cases the same way - the Test Insights tab contributed six. Linking that project's published report added six more without a line of test code: the report is served from this same origin, so the crawler discovered it as a third route and generated a full set of checks against it.

All three engines pass; CI runs Chromium only. The deployment-path tests were verified on Chromium, Firefox and WebKit on 2026-08-12, with identical results - which is what a page resting on ordinary DOM, CSS and atob rather than engine-specific APIs should produce. Re-running all three per push would cost roughly 3.4x the wall clock, Firefox alone being three times Chromium, and would admit engine-specific timing flake into the one signal that gates a deploy. The other two are therefore verified on demand rather than continuously, and a regression specific to either would not be caught by the pipeline. Playwright's WebKit is a build of the engine, not Safari: it shares the renderer, not Safari's platform integration, so this is evidence the page is engine-neutral rather than evidence that Safari works.

CI / Build Status PlaywrightAPWebsiteAutomation CI Pipeline status
Documentation PlaywrightAPWebsiteAutomation README
HTTP Emulators
Repo https://github.com/apolskiy/PublicAP
Tagline Dual Python & Flask HTTP service emulators for REST API fault injection, custom header reflection, and server lifecycle testing
Overview A suite of lightweight HTTP service emulators engineered in Python to enable deterministic quality assurance, REST API fault-tolerance testing, and boundary validation. Comprising an advanced standalone Python HTTP server script and a containerized Flask microservice, the project allows QA automation pipelines to mock external REST endpoints, reflect dynamic headers, and inject edge-case error conditions on demand.
Key Features & Highlights
  1. Core Python HTTP Server Emulator (custom_header_response_to_http_request.py):
    • Full REST API Verb Support: Handles GET, POST, PUT, PATCH, and DELETE methods for complete REST state testing.
    • Payload-Driven Status Selection: The response status is the last three digits of the caller-number field in the JSON body, so 18884400503 returns a 503. This models telephony-style routing, where the dialled number decides the outcome under test. With no body, an X-Caller-Number header supplies the code directly.
    • Server Lifecycle Control Codes: 590 accepts the connection and sleeps 120s without answering, reproducing a hung upstream that a mock cannot imitate; 591 drops and re-opens the listener after 60s; 592 shuts down. Single-threaded by design, which is what makes 590 a genuine stall.
    • Out-of-Band Error Sentinel: Status 999 is deliberately outside the HTTP range, so a client can never confuse "the emulator rejected my request" with a failure it was asked to produce. Returned on a missing or non-numeric caller-number, malformed JSON, or any unexpected exception, after which the process exits non-zero.
    • Session Emulation: 201 returns a fresh session_id (UUID4) per call.
  2. Flask Error Code Emulator (flask_app):
    • Comprehensive Error State Simulation: 21 status codes served on request via /error/<code>: seventeen 4xx (400, 401, 403, 404, 405, 406, 408-417, 419), three 5xx (500, 501, 503), and the non-standard 600, which proves a client tolerates a status outside the registered range. An unlisted code returns 404, so the supported set is discoverable by probing.
    • Configurable Latency Injection: X-Response-Delay-Ms on any request makes the service answer that many milliseconds late, and every response reports what was applied in X-Applied-Delay-Ms - so a delay is assertable from the response rather than from a stopwatch that would also be measuring the test runner. This is the control a client's timeout, retry and backoff code needs and rarely gets, because a real upstream cannot be asked to be slow on demand. It complements rather than replaces the 590 stall above: that one never answers, this one answers late by a known amount, which is what reaches the cases either side of a timeout boundary. A delay that cannot be honoured - malformed, negative, or past the 30s ceiling - is refused with the same 999 sentinel, immediately and without serving the requested status, because a timeout test that passes on a delay which never happened proves nothing. The ceiling refuses rather than clamps for the same reason: a caller silently given 30s when they asked for 60s would draw a conclusion the run did not support.
    • Containerized Execution: Published as a public image (apolskiy/flask_app) on Docker Hub for containerized Linux/CI execution. Built on python:3.14.4-slim and pinned to a Flask-only dependency set: the image carries Flask and its six transitive dependencies and nothing else, confirmed by reading the published layers rather than trusting the requirements file. Latency injection added nothing to that closure - it is time.sleep from the standard library - so a new capability did not widen a public artifact's dependency surface. A .dockerignore at the build-context root keeps local virtual environments, caches and stray logs out of the artifact, taking the published download from 397 MB to 46 MB.
    • Release-Versioned Image Tags: Tags name the emulator release rather than the Python base version - 1.1.0 to pin in CI, 1.0.0 for the pre-latency image, latest for a look around. The previous scheme tagged the base interpreter version, which read like a release of the emulator and was not one. A suite that pulls latest re-pulls a different artifact the day a new one is published, turning a deliberate release into an unannounced change to somebody else's test run; the superseded tag was preserved under its correct number at the same digest, so nothing already pulled stopped matching.
Tech Stack
  • Languages & Frameworks: Python 3.x, Flask, Python Standard Library (http.server)
  • API & Protocols: REST API (full HTTP verbs, 4xx/5xx/6xx fault injection, payload-driven status selection, connection-stall emulation, header-driven latency injection for client timeout and retry paths)
  • DevOps & Containerization: Docker, Docker Hub (apolskiy/flask_app), Linux (Ubuntu container environments)
  • Quality & Tooling: Pytest, pytest-randomly, Pylint (blocking 10.00/10 gate), GitHub Actions, Git
Test Coverage

108 tests against the source, plus 28 against a running container - 136 in total. The 108 run on a four-way matrix of Ubuntu and Windows against Python 3.12 and 3.14. The Flask simulator is driven through its WSGI client for the full 21-code matrix plus real-socket assertions; the caller-number emulator gets a dedicated subprocess on an ephemeral port per test, because its control codes stall or terminate the process and a shared server would make results depend on execution order. Collection order is randomised every run, and a second pass under a fixed seed proves the isolation holds. Latency injection contributed fifteen of those tests, measured against a floor rather than a window - a delay must be at least what was asked for, less the platform's timer granularity, since asserting an upper bound would be asserting that the runner was not busy.

The other 28 test the artifact rather than the code. Two checks existed and never met: the 108 exercise the emulator's source, and a scheduled check in the automation suite reads the published image's dependency closure from the registry. An image built from stale source, or one whose start command no longer works, satisfies both - the source is fine, the layers carry the right packages, and nothing ever asks the artifact to answer a request. These 28 import every expectation from the source tree and assert it against a running container over HTTP, so they do not re-check that the code is correct; they check that the image agrees with the code it claims to be built from. They run twice against two different containers: one built from the commit under test, so a broken Dockerfile fails before anything is published, and one pulled from Docker Hub as a service container, so a published artifact drifting from the source is caught weekly. Two assertions deliberately are not HTTP requests - that the process runs unprivileged, and that the image carries no build files - because a container running as root and shipping its own Dockerfile answers requests perfectly well.

CI / Build Status PublicAP emulator test suite status
Documentation PublicAP README
VM Cluster Deployment
Repo https://github.com/apolskiy/VM-Deployment-and-Configuration
Tagline Deterministic VM cluster provisioning from a credential-free golden image, with an encrypted host inventory and load-balancer failover that is measured rather than asserted
Overview An infrastructure automation and validation harness that deploys a three-node Ubuntu cluster on a Windows VirtualBox host from a single golden image: an Apache load balancer in front of two backends, plus a Go service that serves an AES-256-GCM encrypted inventory of the deployed hosts. Python drives provisioning, Go implements the inventory service, and pytest with Playwright and Allure validates the running cluster. The same suite runs against a Docker Compose stack that models the identical topology, which is what lets a hosted CI runner - where nested virtualization does not exist - still exercise the load balancing and failover behaviour on every push.
Key Features & Highlights
  • Failover Measured, Not Claimed: Each backend is killed outright with VBoxManage controlvm poweroff while the balancer is polled continuously. Across the two runs, 114 requests with 0 errors and 95 requests with 1 error - the single request already committed to the dying member, which took 5.0s, the balancer's own timeout=5 expiring against a machine that no longer exists. So the claim published is not that failover is free, but that it costs at most the one in-flight request and nothing after it. Health checking at hcinterval=2 hcfails=1 is what keeps the window that short: before it was added the same exercise lost roughly 2 requests in 10, because the balancer discovered failures only by failing live traffic.
  • A Distributable Image That Grants Nobody Access: An image that is usable and an image that is publishable pull in opposite directions - anything baked in that lets the author log in lets every downloader log in. The build sanitizes before export, stripping cached registry, git and gh credentials, shell history, and every SSH key including the operational account's own authorized_keys, and fails the export rather than shipping if one survives. Identity arrives per guest instead, from a 66 KB cloud-init seed ISO carrying the hostname, a unique instance id, and the deployer's own public key. Two people deploying the same image get clusters neither can reach in the other's environment.
  • Deploys That Install Nothing: Package installation is the slow, non-deterministic part of any deploy, so it moves into a one-time image build. The Go toolchain is pinned by SHA-256, fetched on the automation host, pushed to the guest over SFTP and re-verified there, because guests reach large downloads unreliably over the bridged link while the host does not. A substituted or corrupted archive fails closed.
  • A Recompile Gate That Knows When It Is Needed: The baked inventory binary carries a SHA-256 fingerprint over every Go source that determines it. configure compares that against the local sources: identical means the baked binary is authoritative and nothing is compiled, different means the Go service was edited since the image was built, so it installs the pinned toolchain, rebuilds, and restamps. The running system is verified against source on every configure and rebuilt only when it has genuinely drifted.
  • Encryption Proven Across Two Languages: The inventory is authenticated encryption, not encoding - nonce || ciphertext || tag, base64-framed, which is byte-for-byte what Go's gcm.Seal emits and what Python's AESGCM.encrypt produces with the nonce prepended. The contract is pinned by a Go test that decrypts a fixture the Python client generated, so the two implementations cannot drift apart silently. The service's decrypted and raw endpoints are tested as a pair: the first proves it can decrypt, the second proves what sits on disk is genuinely ciphertext. Either alone would pass while the other failed.
  • Two Targets, Kept Deliberately Identical: Hosted runners provide no nested virtualization, so the VirtualBox path cannot run in CI at all. Substituting containers is only honest because the two are held to the same hostnames, the same balancer scheduling, and the same health-check settings in both the container config and the vhost the automation renders - a change to one without the other would make the CI result meaningless, which is why they are written to match and commented as such.
  • Both Members Taken Down, Not Just One: CI stops each backend in turn, asserts the survivor served every request with no errors and that the stopped member answered nothing, then restarts it and asserts it rejoins the pool. Failing over correctly for one member and not the other is a real asymmetry, and only exercising both would find it.
  • Publish Scripts Exercised, Not Just Documented: The image publish and pull scripts were documented but had never actually been run, which is how a wrong default path survived in them. A CI job now pushes a stand-in appliance through a real OCI registry using those same scripts and asserts the bytes come back byte-identical. The throwaway artifact is deleted afterwards, and the step degrades cleanly when the cleanup token is absent so a fork still passes.
  • Composable Test Selection: Markers run on two independent axes - tier says what a test needs in order to run, theme says which part of the system it covers - so -m "inventory and not e2e" is a meaningful selection rather than a coincidence. Themes mirror the Allure feature labels, so a pytest selection and the report group the same tests the same way, and every test carries exactly one theme, which makes the themes partition the suite instead of overlapping it.
  • Outages Reported Rather Than Hung: With the jump station stopped, verify exits non-zero naming apjump poweroff in 0.7s and the E2E tier skips in 5.4s with every message naming the unreachable endpoint. A cluster that is down should say so immediately; nothing here waits out a timeout to reach the same conclusion.
  • A Documentation Gate That Reminds Instead of Blocking: Each README section carries the release and date its content last changed, and a CI step reports any section whose content moved while its stamp did not. It is deliberately non-blocking: a documentation gate that fails builds teaches people to route around it.
Tech Stack
  • Languages: Python 3.12+ (provisioning, CLI, test suite), Go 1.26 (inventory service and offline manifest utility), PowerShell (host-side lifecycle scripts), Bash (CI)
  • Virtualization & Provisioning: VirtualBox 7.x via VBoxManage, golden-image export and clone, cloud-init NoCloud seed ISOs, SSH/SFTP, Docker Compose as the parallel container target
  • Networking & Load Balancing: Apache mod_proxy_balancer with mod_proxy_hcheck active health checking, byrequests scheduling, backend identity stamped both as an HTTP header and as a DOM meta tag so a proxy cache cannot make a single-member pool look balanced
  • Cryptography: AES-256-GCM authenticated encryption (Go crypto/cipher and Python cryptography, interoperable byte for byte), Ed25519 key generation, SHA-256 pinning of the toolchain archive and of the compiled binary's sources
  • Quality & Tooling: Pytest with two-axis markers, Playwright, Allure, Pylint at 10.00/10, mypy strict, go vet, a documentation-stamp checker, GitHub Actions, ORAS against GHCR
Test Coverage 174 pytest tests plus 18 Go tests. The tiers split at what a test needs: 155 need no infrastructure and run anywhere, covering cryptography, configuration, the manifest, the recompile-gate fingerprint, host and IP selection, and the survivors-mode logic; the remaining 19 need a live cluster and skip while naming the unreachable endpoint rather than failing, with --require-cluster turning those skips into hard failures for CI. CI runs the unit tier, the Go suite, and then the full E2E suite plus a real failover exercise against the container stack. What cannot run there is stated rather than glossed over: the VirtualBox path needs nested virtualization no hosted runner provides, and is validated on a VirtualBox host instead.
CI / Build Status VM-Deployment-and-Configuration VM Cluster CI status
Documentation VM-Deployment-and-Configuration README
Test Insights
Repo https://github.com/apolskiy/PortfolioTestInsights
Tagline A cross-repository test-results warehouse that keeps four suites' history past the point GitHub deletes it, normalising three incompatible report formats onto one schema
Overview PortfolioTestInsights is a read-only collector that pulls test results out of the GitHub Actions artifacts of the other four repositories in this portfolio, normalises them, and appends them to a durable record. It exists because none of that history was being kept beyond 90 days - and for public repositories 90 days is GitHub's retention maximum, not its default, so no setting prevents the loss. Every reliability question worth asking needs per-test history across runs, and that history was being produced faithfully by every suite and then deleted on a rolling clock. The first backfill captured 13,458 results across 234 tests and 136 runs, reaching back to 2026-05-21 - three days before the oldest of those artifacts expired.
Key Features & Highlights
  • Built Against the Formats That Exist, Not the One Assumed: The design began with the assumption that everything emits JUnit XML. Downloading one artifact per repository disproved it: the largest suite emits no JUnit XML at all, only a generated Allure report, and a third repository publishes raw Allure results whose schema differs from that report in timing, step location and the meaning of name. So three parsers rather than one. Adding --junitxml to the suites would have been a one-line change each and would not have recovered a single historical run, which is the whole reason the format survey came before the code.
  • An Identity Trap Found Before It Corrupted Anything: One suite uses @allure.title, so its results carry a human sentence in name against the real function in fullName. Keying identity on name would have forked a test's history silently every time someone improved a title. Identity is derived from fullName and the display name is stored as a label only - and the four suites now publish assigned IDs (PAWA_, CWA_, PAP_, VMD_) so renaming is free by construction rather than by care.
  • A Confidently Wrong Answer Refused: Half of the site suite's runs are repository_dispatch, where the automation repository's commit is frozen while the deployed site changed underneath. A flakiness detector keyed on head_sha would look at five runs of one commit, see one failure, and report a flaky test - when the input genuinely differed. sut_version is recorded separately and left null rather than guessed, and same-input queries skip those rows. Reporting nothing beats reporting something wrong.
  • Empty Artifacts Recorded as Evidence: An expired artifact is routine; an artifact that exists while containing no results is the opposite - the upload step worked, so the run believed it had something to publish. Each is written to an anomalies partition with the reason, the pattern that missed, and a sample of what the archive actually held, because a wrong glob and an empty report produce the same symptom and want opposite fixes. The first backfill found one and it was real: a failed run that generated an Allure report shell over zero test cases.
  • Diagnostics Captured, Not Summarised: Every non-passing result carries its full stack trace alongside its message, read from whichever of three places the source format keeps it. A message says a test failed; only the trace says where. All 34 failures in the backfill carry one.
  • Coverage Gaps Represented Rather Than Averaged Away: source_format, has_steps and has_trace sit on every row, so "no failing steps recorded" can always be told from "this format cannot express any". JUnit carries no steps, and only 13 of 174 sampled results from one suite carry them - a step-level report that skipped this check would describe a fraction of the corpus as though it were the whole.
  • Append-Only Text as the Record, SQLite as the Index: Committing a database file was the obvious choice and the wrong one: a binary rewritten daily stores a full new copy in history on every commit and cannot be reviewed in a diff. The durable record is NDJSON partitioned by repository and month, so appends produce small deltas and a bad ingestion is visible in review. The queryable index is derived and gitignored - generated things do not go in git.
  • Absence Distinguished From Success: A test missing from a run is recorded as not_run, never omitted and never conflated with skipped, which is reserved for what a format explicitly reported. Deselection by marker is normal in these suites, so without this "never failed" would be indistinguishable from "has not run since May". Synthesis is bounded by each test's observed lifetime, because marking runs before a test existed would manufacture history rather than record it.
  • A Lint Gate That Cannot Pass by Finding Nothing: make lint enumerates tracked files from git, and git returns nothing until the first git add - at which point pylint exits 0 having linted zero files. The recipe fails loudly on an empty list, because a gate that passes by finding nothing is exactly the defect that left a sibling repository's linter silently disabled for months.
  • An Error Budget That Was Calibrated Before It Was Published: Duration is held to a budget rather than a per-run assertion, because one slow run failing a build is noise while sustained degradation is signal. The objective is deliberately relative - a multiple of each test's own established median - since no suite here declares a per-test duration SLA and an absolute threshold would have to be seeded from the very history it then judged. The first draft of it was wrong, and the data said so immediately: it reported 51 of 225 tests as having spent their budget, because the median test in this record has a 1 ms baseline and 145 of 234 sit under 10 ms - so a 3x tolerance means "breached at 3 ms", which is timer granularity rather than slowness anyone experiences. A measured 100 ms floor now excludes those tests outright, with the measurement recorded beside the number in the config. With it in place, 78 tests carry a verdict and none has breached. It reports rather than gates: the suite that owns an SLO is the thing entitled to fail a build over it.
  • Deliberately Not a Machine-Learning Project: Thirty-one failed runs across the whole portfolio, most of them pipeline defects since fixed, is not a training set - and "same input, differing outcome" is a GROUP BY, not a prediction. The store is built first; the question of whether a model earns its place is left to be answered on evidence rather than on aspiration.
Tech Stack
  • Language: Python 3.14 with strict type hints throughout and from __future__ import annotations
  • Ingestion: GitHub Actions REST API via requests, paginated, with Retry-After handling and bounded exponential backoff; selective in-memory zip extraction so only the files a parser needs are decompressed
  • Formats Parsed: raw Allure results, generated Allure report documents, and JUnit XML - one parser each, selected by configuration rather than by a branch in code
  • Storage: append-only NDJSON partitioned by repository and month, idempotent on a five-field row key; SQLite as a derived, gitignored index
  • Configuration: YAML, with named regex groups in artifact patterns so a build matrix becomes parameter columns instead of four near-identical entries
  • Quality & Tooling: Pylint at a blocking 10.00/10 with name-length rules enforced by configuration, a Makefile owning every invocation, GitHub Actions
Test Coverage Ingestion is v0.1.0 and the unit tier is not yet shipped, which is stated here rather than implied by an absent row. The ordering was deliberate and driven by a date: the oldest artifacts in the portfolio expired three days after the first backfill, so the data was captured first and everything reproducible was left for after. Tests will run against recorded fixture artifacts - one per source format - rather than the live API, because a parser test that needs the network is not a unit test and would break the day an artifact expires. CI gates on static analysis in the meantime, and does not pretend to a test job that would collect nothing.
CI / Build Status PortfolioTestInsights CI status
Documentation PortfolioTestInsights README
Live Report Current reliability report - regenerated from the record on every collection, so the figures on it cannot drift away from their source. Nothing on that page is written by hand.
© 2025-2026 Aleksandr Polskiy. All rights reserved.