Regex for QA Engineers: Practical Patterns for Test Automation
A hands-on guide to regular expressions for QA engineers. Learn the regex patterns you'll actually use in Playwright, Jest, and API testing — with real examples for validating emails, phone numbers, dates, UUIDs, and log parsing.
Regular expressions get a reputation for being cryptic and write-only. But for a QA engineer, a handful of well-understood patterns cover the vast majority of real-world use cases. You don't need to master every regex feature — you need to know the patterns for the things you actually test: emails, phone numbers, dates, IDs, URLs, and log messages.
This guide focuses on practical patterns you can use directly in your test code, with explanations of why each pattern is written the way it is.
How regex matching works in JavaScript
JavaScript's regex engine is what powers every testing framework — Playwright, Jest, Cypress, and Vitest all use the same V8 regex implementation. Two methods are most relevant in testing:
test(string) — returns true or false. Use this for assertions.
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
emailPattern.test('alice@example.com') // true
emailPattern.test('not-an-email') // falsematch(regex) — returns the match array or null. Use this when you need to extract the matched value.
const logLine = '2025-12-14T10:30:00Z [ERROR] Payment service timeout after 5000ms'
const timestampMatch = logLine.match(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z/)
console.log(timestampMatch?.[0]) // "2025-12-14T10:30:00Z"The flags you need to understand
Flags modify how the pattern engine processes the string. In test automation, these three matter most:
| Flag | Effect | When to use |
|---|---|---|
g | Global — find all matches, not just the first | Counting occurrences, extracting all matching values |
i | Case insensitive | Email validation, status strings, error messages |
m | Multiline — ^ and $ match line boundaries | Parsing multi-line log output |
// Without `i` flag — only matches "ERROR"
/ERROR/.test('error') // false
// With `i` flag — matches any case
/ERROR/i.test('error') // true
/ERROR/i.test('Error') // true
/ERROR/i.test('ERROR') // trueEssential patterns for test assertions
Email address
// Basic — catches most invalid emails without being overly strict
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
// Use in Jest
expect(user.email).toMatch(emailRegex)
// Use in Playwright page assertion
await expect(page.locator('[data-testid="user-email"]')).toHaveText(emailRegex)Why not use a "perfect" email regex? The RFC 5322 compliant email regex is 6,000+ characters long and still misses edge cases. For testing that a field looks like an email, the simple pattern above is correct 99.9% of the time.
UUID (version 4)
const uuidV4Regex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
expect(response.body.id).toMatch(uuidV4Regex)The 4 in the third group and [89ab] in the fourth group are the distinguishing features of UUID v4. If you just need to check that something looks like any UUID format, use the looser pattern:
const uuidAnyRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iISO 8601 date-time
// Matches: 2025-12-14T10:30:00Z or 2025-12-14T10:30:00.000Z or 2025-12-14T10:30:00+05:30
const isoDateTimeRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$/
expect(order.createdAt).toMatch(isoDateTimeRegex)
// Just a date (no time component)
const isoDateRegex = /^\d{4}-\d{2}-\d{2}$/
expect(event.date).toMatch(isoDateRegex)Phone number (US format)
// Matches: +1-555-123-4567, (555) 123-4567, 555.123.4567, 5551234567
const usPhoneRegex = /^(\+1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}$/
expect(user.phone).toMatch(usPhoneRegex)For international numbers, consider whether you need to validate the format at all in your tests — often checking that the field is non-empty and contains only digits, spaces, hyphens, and parentheses is sufficient.
URL
// Matches http and https URLs
const urlRegex = /^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&/=]*)$/
expect(product.imageUrl).toMatch(urlRegex)
// More specific — only HTTPS
const httpsUrlRegex = /^https:\/\/.+/
expect(paymentRedirectUrl).toMatch(httpsUrlRegex)Credit card number (redacted format)
APIs should never return full card numbers. Assert that they're returning the masked format:
// Matches: **** **** **** 4242 or ****4242
const maskedCardRegex = /^(\*{4}\s?){3}\d{4}$|^\*{4}\d{4}$/
expect(payment.cardNumber).toMatch(maskedCardRegex)Regex in Playwright
Playwright's toHaveText, toContainText, and getByText all accept regex patterns directly. This is significantly more robust than exact string matching for dynamic content.
// Exact text — fragile, breaks if punctuation changes
await expect(page.locator('.status')).toHaveText('Order confirmed!')
// Regex — robust, ignores surrounding whitespace and exact punctuation
await expect(page.locator('.status')).toHaveText(/order confirmed/i)Finding elements with dynamic IDs
// Don't use exact id when it changes per-session
await page.locator('[data-testid="order-ord_abc123"]') // fragile
// Use regex to match the pattern
await page.locator('[data-testid^="order-"]') // CSS starts-with
await page.getByTestId(/^order-/) // regexAsserting error messages
Error messages often include dynamic values (field names, limits, actual values). Regex lets you assert on the structure without hardcoding the dynamic part:
// The exact count might vary — assert the pattern
await expect(page.locator('.error-message')).toHaveText(/must be at least \d+ characters/)
// Assert it contains the field name but not the exact wording
await expect(page.locator('.field-error')).toHaveText(/email/i)Regex in Jest
Jest's expect(value).toMatch(regex) works on strings. For arrays, use expect.arrayContaining with expect.stringMatching:
// Assert all emails in a list match the pattern
const users = await api.getUsers()
const emails = users.map(u => u.email)
expect(emails).toEqual(
expect.arrayContaining([
expect.stringMatching(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)
])
)
// Assert at least one item in the response contains an error about "email"
const errors = response.body.errors
expect(errors.some(e => /email/i.test(e.field))).toBe(true)Parsing log output with regex
Log parsing is a QA-adjacent task that comes up during CI failure investigation, performance analysis, and monitoring setup. Regex makes it tractable.
Extracting error counts from CI output
const ciLog = `
Running 124 tests across 8 workers
✓ 119 passed (45.2s)
✗ 5 failed
`
const failedMatch = ciLog.match(/(\d+) failed/)
const failedCount = failedMatch ? parseInt(failedMatch[1], 10) : 0
console.log(`Failed tests: ${failedCount}`) // 5Parsing structured log lines
// Log format: 2025-12-14T10:30:00Z [LEVEL] Message
const logPattern = /^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z)\s+\[(\w+)\]\s+(.+)$/m
const logLine = '2025-12-14T10:30:00Z [ERROR] Database connection timeout'
const match = logLine.match(logPattern)
if (match) {
const [, timestamp, level, message] = match
console.log({ timestamp, level, message })
// { timestamp: '2025-12-14T10:30:00Z', level: 'ERROR', message: 'Database connection timeout' }
}Counting specific events in a log file
import { readFileSync } from 'fs'
const logs = readFileSync('test-run.log', 'utf8')
const errorLines = logs.match(/\[ERROR\]/gm) ?? []
const warnLines = logs.match(/\[WARN\]/gm) ?? []
console.log(`Errors: ${errorLines.length}, Warnings: ${warnLines.length}`)Building and testing patterns
Regex patterns are easy to get wrong in subtle ways — a pattern that works for your test cases might fail on edge cases you haven't considered. Using an interactive tester while building patterns eliminates this.
The Regex Tester tool on this site shows live match highlighting as you type. Key things to verify:
- Boundary anchors — does your pattern use
^and$to prevent partial matches? Without anchors,/\d{4}/matches in"abc1234def". - Special character escaping —
.in regex matches any character. If you mean a literal dot, use\.. - Edge cases — test the empty string, very long strings, and strings containing regex special characters (
[,],(,),*,+,?,{,},|,^,$,.,\).
// Common mistake: unescaped dot matches any character
/example.com/.test('exampleXcom') // true (wrong)
/example\.com/.test('exampleXcom') // false (correct)Quick reference: regex syntax
| Pattern | Meaning | Example |
|---|---|---|
. | Any character except newline | /a.c/ matches abc, aXc |
\d | Digit [0-9] | /\d{4}/ matches 2025 |
\w | Word char [a-zA-Z0-9_] | /\w+/ matches hello_123 |
\s | Whitespace | /a\sb/ matches a b |
^ | Start of string (or line with m) | /^hello/ |
$ | End of string (or line with m) | /world$/ |
+ | One or more | /a+/ matches a, aa, aaa |
* | Zero or more | /a*/ matches ``, a, aa |
? | Zero or one | /colou?r/ matches color, colour |
{n,m} | Between n and m times | /\d{2,4}/ matches 2–4 digits |
[abc] | Character class | /[aeiou]/ matches any vowel |
[^abc] | Negated class | /[^0-9]/ matches non-digits |
(a|b) | Alternation | /(cat|dog)/ matches cat or dog |
(...) | Capture group | /(hello) (world)/ captures both words |
(?:...) | Non-capture group | Groups without capturing |
\b | Word boundary | /\btest\b/ matches test not testing |
Stay ahead in AI-driven QA
Get practical tutorials on test automation, AI testing, and quality engineering — straight to your inbox. No spam, unsubscribe any time.
Discussion
Sign in with GitHub to comment · powered by Giscus