Skip to content

Open to Test Automation & QA Engineer roles

Szymon Rojek

A number can match and still be wrong.

Four years testing financial data migrations at Equiniti: SQL reconciliation, business logic and root-cause analysis. I taught myself to code, so I automate tests with Playwright and build full-stack apps to practise.

Based in
Hove, UK
Right to work
UK, full
Relocation
Open

100% tester

What colleagues say

“…you will be fortunate if you get to work with him.”

Jim Grinsfelder, Senior Software Engineer at Equiniti
  • From a developer's perspective, what I value most is his commitment to getting to the root cause of issues—not just reporting symptoms. … His bug reports are always thorough, well-structured, and contain all the key details needed to move quickly from diagnosis to resolution.

    Oskar WielanowskiSQL Developer at EQTek, worked with me on the same team · May 2025
  • His write-ups included everything needed to reproduce the conditions and defects he was reporting. He's got a solid understanding of SQL and the context within which we were both working. I'd gladly work with him in the future and you will be fortunate if you get to work with him.

    Jim GrinsfelderSenior Software Engineer at Equiniti, worked with me on a different team · January 2025
  • Szymon is committed to his tasks and always tries to get to the bottom of the issues. He is persistent in striving to expand his knowledge.

    Dawid KwaśnikDatabase Consultant (data migration) at Endava, worked with me on a different team · February 2024

Read them on LinkedIn

How I test at work

At Equiniti I test financial and shareholder data as it moves between systems. I try to look at a migration from above, so I see the same record as data, as business logic and as code. Clients and figures are confidential, so this is my approach, not a project report.

Beyond the obvious bugs

Sometimes I was right. That gave me far more satisfaction than finding the obvious bugs.

More than once I found bugs not only in the data that came with a code drop after a deployment, but in the stored procedures themselves.

Sometimes there was no data to test yet. I like to analyse and dig, so I read the stored procedures and checked them against the business requirements and the mapping documents, and asked the solution designers more questions. Several times I set up a call with the developer who wrote the code, to go through an interesting case together.

  1. Understand the rules, then design the tests

    I turn requirements, mapping documents and transformation rules into positive and negative test cases, each traceable back to the rule it checks and peer-reviewed before anything is run.

  2. Reconcile with SQL and check the logic

    Record counts, totals, field-by-field values, empty values, duplicates and relationships between tables. A number can match and still be wrong, so I also check results against the business rules and read the stored procedures that transform the data.

  3. Trace each defect to its root cause

    Is it the data, the transformation logic, a stored procedure or the requirement itself? I log it in Azure DevOps with reproduction steps and evidence, and retest the fix with the developers.

Why it matters: In financial data, one wrong field can mean a wrong holding or a wrong payment. Reconciliation proves that nothing was lost or changed on the way.

Experience

Sep 2022 – now

Equiniti, Worthing

Financial services and shareholder registry

Software Test Engineer

  • Validate financial and shareholder data across migration and ETL projects, including conversion and deconversion.
  • Estimate effort and work in an Agile team with developers, architects and stakeholders.
  • Use GitHub Copilot agents and write agentic testing procedures for the test team, with validation steps and human oversight.
  • Pilot in-house AI agents that write tests from user stories and mapping documents: find the gaps in their output and review them with the senior engineers who build them.

Projects

Two projects I built in my own time to practise development, and tested the way I test at work. Each one is shown from both sides: how I built it, and how I proved it works.

  • Talent Outreach: job offers to the right candidates

    Job offers to the right candidates: matched by role and budget, personalised, and skipping anyone contacted in the last 14 days.

    • Full-stack TypeScript
    • Unit, API & e2e tests
    • Staging → production
    Live demoCode
  • This website: built and tested like a real product

    The page you're reading, rebuilt in 2026 and tested like a real product.

    • React + TypeScript
    • Playwright & axe in CI
    • Lighthouse budgets
    Code

Case study

Talent Outreach: job offers to the right candidates

  1. Brief

    A small recruitment agency keeps its candidates in a spreadsheet. Every new job offer means filtering by hand, pasting addresses into Gmail and writing “Dear candidate”, with the risk of leaking addresses or contacting the same people too often.

  2. Research

    Spreadsheet and GmailMy design
    Filter the sheet by hand and copy the addresses.Pick a role and a budget, and the matching candidates are found.
    “Dear candidate” for everyone.Every email uses the candidate's name and the role, and a mistyped placeholder is caught before sending.
    Nobody remembers who got which offer.Candidates contacted in the last 14 days are skipped by default, and the list shows when each one was last contacted.
    Consent is a column nobody updates.Only candidates open to offers get emails, and the opt-out link in every email updates the status by itself.
    One address in CC instead of BCC leaks everyone's email.One email per candidate, so nobody sees the others.
    What was sent is buried in the Sent folder.An outbox for each job offer shows who got it and every email exactly as it was sent.
  3. Solution

    • A server-only path to the data: the Airtable token never reaches the browser.
    • A one-click “Try the demo” login that exists only where the demo is switched on; API clients use a password, with a 15-minute lockout after 5 wrong attempts.
    • The browser suggests the recipients, and the server has the last word: it sends only to candidates open to offers.
    • Every email is built on the server from one template: personalised, escaped and with a signed opt-out link. The preview uses the same template.
    • An outbox instead of real sending, so the public demo can't email anyone.
    • Staging before production, a nightly demo reset followed by a read-only check of the live app, and error monitoring without personal data.
  4. Value

    • Fewer costly mistakes: no CC leaks, no offer twice within two weeks and no email to anyone without consent.
    • From a role and a budget to personalised offers in one flow, with a preview before every send.
    • Safe to hand over and change: tests at every level, CI on every pull request and staging before production.

    Not built yet: real delivery. Every email is built, checked and saved to the outbox; sending it through an email service is one more transport in one file. Airtable's free plan also caps a base at 1,000 records and 1,000 API calls a month, so a bigger talent pool needs a paid tier.

Automation view

How I proved it works

Tested from a single function to the live app

  • 200+ unit tests: Jest and supertest on the server, Vitest and React Testing Library on the client.
  • 63 Playwright user flows on the production build against a fake Airtable, from a job offer draft to the outbox, opting out and phones. CI needs no secrets and never touches real data.
  • A Postman collection run by Newman in CI tests every endpoint from the outside, including refused logins, a duplicate email and a second send of the same offer; its read-only part checks the live app after every nightly reset.
  • Lighthouse audits 7 screens on every pull request and fails it if accessibility, best practices or SEO drop below 100.
  • Jest
  • supertest
  • Vitest
  • React Testing Library
  • Playwright
  • Postman
  • Newman
  • Lighthouse
  • GitHub Actions

Developer view

How I built it

A 2021 email app rebuilt into a focused recruitment product

  • React 19 client with shadcn/ui, TanStack Query and React Hook Form; an Express API in TypeScript on Node.js 24.
  • Signed login tokens valid for 8 hours. The one-click demo login exists only where it is switched on, and the lockout uses the visitor's real address behind Cloudflare, so one person can't block others.
  • Recipients by role, budget and last contact; the server re-checks consent and builds every email with a signed opt-out link.
  • Follows Airtable's paging and rate limits, resets the demo every night, reports errors to Sentry without personal data, and deploys dev to staging and main to production.
  • React 19
  • TypeScript
  • Express
  • Airtable API
  • Tailwind CSS
  • shadcn/ui
  • Sentry
  • Render
Read the full case study

Problem

The app started in 2021 as an email CRUD client that sent emails through EmailJS straight from the browser. The Airtable key sat behind an Express proxy, but the login ran only in the browser: anyone who called the API directly could read, change or delete every record. My first review found 11 bugs.

In 2026 I rebuilt it to production standard and then narrowed it from a generic newsletter tool to one recruiter's problem: the right job offer to the right candidates, and not to anyone contacted in the last 14 days.

Constraints

  • Airtable is the database: a third-party REST API with rate limits and pages of 100 records.
  • Candidate data is personal data: consent, an opt-out in every email and no personal data in monitoring.
  • A public demo anyone can enter with one click: no real emails, visitors change the data, and only made-up data belongs in it.
  • Free hosting (Render) that sleeps after inactivity and blocks outgoing email (SMTP).
  • One maintainer, so every change has to be checked automatically and on staging before production.

Architecture

Runtime: the browser never talks to Airtable

  1. React app
  2. Express API under /api with a signed login token
  3. Server checks consent, escapes text, signs opt-out links
  4. Airtable: candidates, job offers, outbox, feedback

Delivery: feature branch to production

  1. Feature branch off dev
  2. Pull request: type check, lint, unit tests, build
  3. Playwright e2e, Postman API tests and Lighthouse against a fake Airtable
  4. Merge to dev, auto-deploy to staging
  5. Check staging
  6. Release pull request dev to main, CI again on all changes together
  7. Auto-deploy to production

Key decisions and trade-offs

DecisionWhyTrade-off
Server-side login with signed tokens and a lockout after 5 wrong passwordsA browser-only login didn't protect the API at all.The lockout lives in memory and resets when the free server restarts. It counts the real address behind Cloudflare, so one visitor can't block others.
A one-click demo login, only where the demo is switched onA reviewer should try the app in one click, without a password.Any server without the setting has no passwordless door; API clients still log in with the password.
The browser suggests the recipients, the server has the last wordFiltering by role, budget and last contact needs a fast, interactive list.The server re-checks consent on every send, so an outdated or edited list can't reach someone who opted out.
An outbox instead of real sending in the demoAnyone can enter the public demo, and the free host blocks SMTP.Nobody gets the emails; each one is saved and can be opened. Real delivery is one more transport in one file, not built yet.
End-to-end, API and Lighthouse runs against a fake AirtableTests must never touch real data, and CI shouldn't need secrets.The fake can drift from the real API, so it copies Airtable's record shape and errors.
Nightly reset of the demo data, then a read-only check of the live appVisitors change the data, and the next reviewer should see a working example.New examples are created before the old ones are deleted, and the reset is skipped when nothing changed, to save API calls. A broken demo fails the run.
One recruiter problem instead of a generic newsletter toolA problem that is solved end to end is easier to explain, to use and to test.Fewer features: the app only sends job offers to candidates.
Error monitoring without personal dataI need to see failures on the live demo without collecting data I don't need.Sentry gets no IPs, headers or request bodies, so some errors are harder to reproduce.
Staging before production, with a hotfix pathChanges are checked on a live copy before real users see them.Two environments to keep in sync, and a hotfix must be merged back into dev.

Testing, performance, accessibility and security

Testing
Server tests with Jest and supertest cover every endpoint, login and limits, sending and templates, opt-out links, the CSV import, feedback, the demo reset and privacy in monitoring, with Airtable mocked. Client tests with Vitest and React Testing Library cover validation, CSV, search, sorting and filtering, the API client and choosing recipients by role, budget and last contact. Playwright runs 63 user flows on the production build, from a job offer draft to the outbox, opting out, unsaved changes, dark mode and phones. A Postman collection of 30 requests, run by Newman in CI, tests every endpoint from the outside, and its read-only smoke folder checks the live app after every nightly reset.
Performance
The server follows Airtable's paging, so lists show every record and not only the first 100. Approved feedback is kept in memory for 30 seconds and the nightly reset is skipped when nothing changed, both to stay within Airtable's API limits. Lighthouse reports performance on 7 screens; its limit only catches a real slowdown, because scores vary on shared CI machines. On the free plan the main cost is a cold start of up to a minute, which the README tells visitors about.
Accessibility
The UI is built on shadcn/ui and Radix primitives, which handle keyboard and screen-reader support for dialogs, menus and side panels. Lighthouse audits 7 screens, logged out and in, on every pull request and fails it below 100 for accessibility; it already caught a low-contrast avatar and a button without a name. End-to-end tests find controls by their role and accessible name, and phone layouts have their own tests.
Security
The Airtable token lives only on the server and error responses no longer leak it. Login tokens are HMAC-signed and expire after 8 hours, with constant-time comparisons and a lockout on the visitor's real address. The demo login exists only where it is switched on. Opt-out links have their own signature, user text is escaped in every email, the server has no CORS, and public feedback has a bot trap, a rate limit and moderation.

Lessons learned

  • A review found the Airtable token leaking in error responses. Anything a server returns on failure needs the same care as a success response.
  • Unchecking every recipient sent the email to all of them: an empty selection was treated as “no filter”. Edge cases like an empty list deserve their own test.
  • Lists silently stopped at 100 records because Airtable pages its results. Testing with more data than one page is cheap and catches this.
  • Monitoring can leak too: opt-out tokens in page addresses had to be removed from error reports before they were sent.
  • Lighthouse in CI caught what the tests missed: a low-contrast avatar and a button without an accessible name.
  • A public demo needs product decisions, not only code: an outbox instead of real email, a nightly reset, a one-click login and a request for made-up data.
  • A fake API makes end-to-end tests fast and safe, but only if it behaves like the real one, including its errors.

Case study

This website: built and tested like a real product

  1. Brief

    My first website, from 2021, was a learning project: it had no tests, and its project list could fail for a visitor. I rebuilt it to show my testing skills in practice, on a site that never breaks.

  2. Research

    Before the rebuildMy design
    Projects loaded from the GitHub API, which allows 60 requests an hour per address, so a visitor on a shared office network could see an error.All content is static, so there is nothing to time out or rate-limit.
    No automated tests and no CI.Every change passes unit, end-to-end, accessibility and Lighthouse checks before it can deploy.
    Reloading in dark mode flashed a white page.The theme is painted before JavaScript loads.
    The HTML was empty until JavaScript ran, so search engines and AI tools that don't run it saw a blank page.The build writes the whole page into the HTML, case studies included.
    Google Fonts blocked rendering, and the photo was three times larger than displayed.A self-hosted font and a right-sized photo raised mobile performance from 0.84 to about 0.9.
  3. Solution

    • Content in typed data files and a static build: no server, no runtime API calls.
    • A test pyramid: unit tests for logic, Playwright for what visitors see, axe for accessibility.
    • Every push passes audit, lint, type check, tests and Lighthouse budgets before it can deploy.
    • A protected main branch: changes reach the live site only through a pull request I review, so an AI coding assistant can't ship on its own.
  4. Value

    • Free hosting on GitHub Pages.
    • Nothing can fail for a visitor: no API calls, no loading or error states.
    • Every change is verified before release, and anyone can check it through the public tests and CI badge.

Automation view

How I proved it works

Nothing ships unless every gate passes. Every push runs this pipeline:

  • ESLint and a strict TypeScript type check
  • Vitest unit and component tests with an 80% coverage gate
  • 40+ Playwright tests on desktop and mobile, with axe scans of both themes
  • Lighthouse budgets on the median of three runs
  • npm audit on every push and weekly Dependabot updates
  • A fixture that fails any test calling the GitHub API

Every check passes → deploy to GitHub Pages

  • Vitest
  • Testing Library
  • Playwright
  • axe
  • Lighthouse CI
  • Stryker
  • GitHub Actions

Developer view

How I built it

A static React app with nothing that can fail for a visitor

  • React 19 with strict TypeScript, Redux Toolkit for the theme and styled-components with typed light and dark themes.
  • Content lives in typed data files, so there are no runtime API calls and no loading or error states.
  • The theme follows the OS live, a saved choice wins, and it's painted before JavaScript loads, so a dark reload never flashes light.
  • A self-hosted font and a right-sized photo raised mobile Lighthouse performance from 0.84 to about 0.9.
  • React 19
  • TypeScript
  • Redux Toolkit
  • styled-components
  • Vite
  • GitHub Pages
Read the full case study

Problem

A CV can say “Playwright” and “CI/CD”, but a recruiter can't check it. I wanted the portfolio itself to be the evidence: readable by HR in 30 seconds, and able to stand up to a hiring manager's review of the code.

The earlier version loaded my projects from the GitHub API in the visitor's browser. That API allows 60 unauthenticated requests per hour per IP address, so a recruiter on a shared office network could see an error box in the middle of a tester's portfolio.

Constraints

  • Free static hosting on GitHub Pages at my own domain, with no server.
  • One maintainer with limited time, so automation has to catch regressions.
  • Privacy: no phone number anywhere on the site.
  • WCAG AA colour contrast in both the light and the dark theme.
  • An older Mac (macOS 12) where Playwright can't install its own browser, so local runs use the installed Chrome.

Architecture

Runtime: what a visitor's browser loads

  1. Content in TypeScript data files
  2. React components with light and dark themes
  3. Vite production build
  4. Whole page prerendered into the HTML
  5. Static files on GitHub Pages
  6. Browser: no API calls

Delivery: every change goes through these gates

  1. Commit on dev
  2. Dependency audit
  3. Lint and type check
  4. Unit tests with 80% coverage gate
  5. Production build
  6. Playwright and axe on desktop and mobile
  7. Lighthouse budgets
  8. Pull request to main, merged by me
  9. Deploy to GitHub Pages

Key decisions and trade-offs

DecisionWhyTrade-off
Static project data instead of the GitHub APIRate limits on shared networks could show recruiters an error.Descriptions are updated by hand. An end-to-end fixture fails if the page ever calls the GitHub API again.
Test pyramid: Vitest for logic and components, Playwright for real-browser behaviour, axe for accessibilityFast feedback on logic, and real confidence in what visitors see.End-to-end tests take about a minute, so they cover only user-visible behaviour.
An 80% coverage gate that leaves out stylesCoverage should measure logic, not CSS.Styles are checked by the end-to-end and axe tests instead.
Paint the theme before JavaScript loadsReloading in dark mode briefly flashed a light page.The background colours live in two places. A unit test keeps them in sync.
Prerender the page into the HTML at build timeSearch engines and AI tools that don't run JavaScript saw an empty page.Components must render without a browser. A unit test renders the page in Node and fails if one reaches for it.
Self-hosted font and a right-sized photoGoogle Fonts blocked rendering, and the photo was three times larger than displayed.Font updates are manual: the files live in the site and are preloaded in the HTML.
Protected main branch with pull requests I merge myselfEvery push to main deploys. The rule also stops an AI coding assistant from shipping on its own.Even a one-line fix needs a pull request and a green pipeline.

Testing, performance, accessibility and security

Testing
Vitest and Testing Library cover logic and components. Playwright runs against the production build in desktop and mobile Chrome, with tests grouped by page section and named steps. Role-based locators also check that every control has an accessible name.
Performance
Lighthouse runs three times on every push and fails CI if the median performance score drops below 0.85 or layout shift goes above 0.1. Self-hosting the font and resizing the photo raised the mobile score from 0.84 to about 0.9 and brought the first paint forward from 3.0 s to 2.2 s. The build also writes the whole page into the HTML, so it can be read without JavaScript, and in the light theme React takes it over as it is, so the photo isn't drawn twice.
Accessibility
axe scans the page in the light and dark themes with every case study expanded, and any serious or critical violation fails the build. Lighthouse accessibility must stay at 95 or above. The page uses semantic headings, landmarks and labelled lists.
Security
No secrets and no runtime API calls. npm audit fails CI on high-severity advisories in the code shipped to visitors, and Dependabot proposes updates every week. External links use rel=“noreferrer”. Development-only tools still carry some advisories, but they never reach the browser.

Lessons learned

  • A bug I couldn't reproduce, a light flash on reload, became solvable once I wrote a failing test that recorded the background colour on every frame.
  • A fix can look broken because of browser caching. I now check what the server actually serves before changing the code again.
  • When removing well-tested code lowered coverage, I added tests for real branches instead of lowering the threshold.
  • End-to-end tests hard-code the text a visitor sees instead of importing it from the data files, so a content mistake can't pass by testing itself.
  • Lighthouse CI judges the best of three runs by default. I switched to the median so the budget can't pass by luck.
  • Mutation testing with Stryker showed that 80%+ coverage hid real gaps: a saved dark theme, the state on page load, the commas between interests and the content of each case-study section were never checked. After targeted tests, every mutant in those files was caught. The tool needed checking too: a runner bug first reported 13.6% because no tests ran for most mutants.
  • A flaky end-to-end test pointed to a real race: a case-study link that changed the address just after the page loaded was ignored, because React had not attached its listener yet. A unit test now reproduces it, and the page reads the address again once the listener is ready.
  • After the redesign, Lighthouse performance dropped below the budget. The cause was a small pulsing dot: its animated shadow made the browser repaint on every frame. Animating only its size and opacity moved the work off the main thread, and the score went back above the budget.
  • A photo that seemed to flicker on every reload was the text reflowing: the page first appeared in a fallback font, and the photo moved when the web fonts arrived. Preloading the fonts in the HTML fixed it and brought the first paint forward from about 2.2 s to 1.6 s.

Skills

Testing & data

  • Data migration & ETL testing
  • SQL reconciliation (T-SQL, SQL Server)
  • Test design & traceability
  • Defect root-cause analysis
  • AI-assisted testing

Test automation

  • Playwright & TypeScript
  • API tests: Postman, Newman, supertest
  • Vitest, Jest, Testing Library
  • axe & Lighthouse
  • Quality gates in GitHub Actions

Programming

  • TypeScript & React
  • Node.js & Express
  • REST APIs
  • Redux Toolkit
  • Signed-token auth, Git flow

Tools: Azure DevOps · SQL Server Management Studio · Postman · Git · GitHub Actions · VS Code · GitHub Copilot · Claude

Languages: Polish (native) · English (professional working proficiency)

Still learning

Next up

ISTQB Certified Tester, Foundation Level

Preparing for the exam now.

  • 2026

    Microsoft AI courses

    Generative AI, Copilot, responsible AI

  • 2025

    Artificial Intelligence course

    John Paul II Catholic University of Lublin

  • 2023 – now

    LinkedIn Learning

    Programming, testing and AI, alongside my job

  • 2022

    React School

    React workshops, final exam passed