How to Match Negative Numbers with Cypress cy.contains()
Match negative numbers reliably with Cypress cy.contains() using numeric values, exact regular expressions, selectors, and stable test strategies.
Use cy.contains(-42) when the rendered value is the numeric text -42. Use an anchored regular expression such as cy.contains(/^-42$/) when the entire element text must equal -42. Add a selector when the element type or region matters, for example cy.contains('output', /^-42$/).
Cypress documents String, Number, and RegExp content for cy.contains(). Its numeric example uses a positive number, so applying the documented Number argument to a negative value is a direct application of that API. See the official cy.contains() documentation.
Choose the matching form
| Test intent | Example | What it matches |
|---|---|---|
| Numeric value | cy.contains(-42) |
The displayed numeric value |
| Exact rendered text | cy.contains(/^-42$/) |
Only text whose complete value is -42 |
| Exact text in a known element | cy.contains('output', /^-42$/) |
An output element with exactly that text |
| Formatted value | cy.contains(/^-\$42\.00$/) |
The exact format actually rendered |
Basic examples
Match a negative number
describe('balance', () => {
it('shows a negative balance', () => {
cy.visit('/account')
cy.contains(-42).should('be.visible')
})
})
The number form is concise when the assertion is about a value and the application renders that value as text.
Match the whole text exactly
cy.contains(/^-42$/).should('be.visible')
A string query is a substring search. Therefore, cy.contains('-42') may also match -420 or Balance: -42. The ^ and $ anchors require the complete rendered text to be -42.
Restrict the candidate element
cy.contains('output', /^-42$/)
.should('have.attr', 'aria-label', 'Current balance')
Passing a selector limits candidates to that element type. This is useful when the same number appears in a heading, table, hidden template, and output control.
Complete Cypress example
describe('negative values', () => {
beforeEach(() => {
cy.visit('/account')
})
it('matches the numeric value', () => {
cy.contains(-42).should('be.visible')
})
it('matches exact text', () => {
cy.contains(/^\-42$/).should('have.text', '-42')
})
it('matches an exact value in an output element', () => {
cy.contains('output', /^\-42$/)
.should('be.visible')
.and('have.text', '-42')
})
it('matches a formatted currency value', () => {
cy.contains('output', /^-\$42\.00$/)
.should('be.visible')
})
it('matches text with surrounding content when intended', () => {
cy.contains('Balance: -42').should('be.visible')
})
})
The slash before the minus sign in /^\-42$/ is optional in JavaScript regex syntax; /^-42$/ is equivalent. Escaping it can make the literal hyphen requirement visually explicit.
Formatting and whitespace
Match the text the user actually sees. These values require different patterns:
// Exactly -42
cy.contains(/^-42$/)
// Exactly -42.00
cy.contains(/^-42\.00$/)
// Currency symbol before the value
cy.contains(/^-\$42\.00$/)
// Parentheses used for negative accounting values
cy.contains(/^\(42\.00\)$/)
// Label and value in one element
cy.contains(/^Balance:\s*-42$/)
Cypress collapses runs of whitespace before matching in ordinary elements, while whitespace in pre elements is preserved. The regular expression must still describe the text structure your page renders. If formatting is locale-dependent, prefer a stable attribute or assert the formatted value separately.
Selectors, scope, and yielded elements
cy.contains() yields at most one matching element. Cypress normally chooses the deepest match, but it gives preference to certain elements such as buttons, links, labels, and submit inputs when the matching text is inside them. Use a selector or scope the query when that preference is not what you want.
// Limit the search to a panel
cy.get('[data-testid="balances"]')
.contains('output', /^-42$/)
// Search within a table row
cy.contains('tr', 'Overdraft')
.contains('td', /^-42$/)
// Prefer a stable test hook when the number is incidental
cy.get('[data-testid="current-balance"]')
.should('have.text', '-42')
Dynamic values and retry behavior
cy.contains() is a retryable Cypress query. It keeps looking until the default command timeout expires, so it works when a balance appears after an API request:
cy.intercept('GET', '/api/account').as('account')
cy.visit('/account')
cy.wait('@account')
cy.contains(-42).should('be.visible')
For a value calculated during the test, build the query after the value is known:
const expectedBalance = -42
cy.contains(expectedBalance).should('be.visible')
If the page can legitimately show either a positive or negative value, assert the business rule first and then match the resulting text. Avoid arbitrary waits; wait for a network request, a meaningful selector, or a state change.
Case sensitivity, shadow DOM, and assertions
Case is relevant for text. You can pass { matchCase: false } for string content. With a regular expression, that option behaves like the i flag; do not combine conflicting case options.
cy.contains('balance: -42', { matchCase: false })
cy.contains(/^balance: -42$/i)
Cypress does not traverse shadow roots by default. Enable shadow DOM traversal when the value is inside an open shadow root, or query through a scoped .shadow() chain:
cy.contains('my-balance', -42, { includeShadowDom: true })
cy.get('my-balance')
.shadow()
.contains(-42)
When to use a data attribute instead
Use text matching when the text is part of the behavior under test: a displayed balance, an error message, or a status that users need to see. Use a stable data-testid or similar attribute when the number is incidental and may be reformatted without changing behavior.
// Text is the behavior
cy.contains(/^Overdrawn: -42$/).should('be.visible')
// The value is incidental; the element identity is the behavior
cy.get('[data-testid="balance-value"]')
.should('have.text', '-42')
Negative assertions and absence checks
There is no built-in negation form of cy.contains(). To assert that a matching element is absent, query a suitable container and use a negative assertion:
cy.get('[data-testid="alerts"]')
.should('not.contain', '-42')
Be careful with negative assertions on dynamic pages. A test can pass before the value has had time to appear. Wait for the request or the page state that proves rendering is complete, then assert absence.
Common errors and fixes
| Symptom | Cause | Fix |
|---|---|---|
cy.contains('-42') matches the wrong element |
String matching is a substring search and Cypress may prefer an ancestor element. | Use /^-42$/, pass a selector, or scope with cy.get(). |
| Numeric form finds nothing | The UI renders formatting such as -$42.00, parentheses, or a label. |
Match the rendered format with a regex or assert the element text directly. |
| Intermittent failure while loading | The query runs before the application finishes updating. | Wait on the relevant request or state selector; rely on Cypress retryability instead of fixed sleeps. |
| Duplicate matches | The number appears in multiple cards, rows, or hidden templates. | Scope to a container and add an element selector. |
| Value is inside a shadow root | Shadow DOM traversal is disabled by default. | Use includeShadowDom: true or chain .shadow(). |
| Negative assertion passes too early | The test checks absence before the application has rendered the value. | Wait for the request or a ready marker before asserting absence. |
| Regex does not match | The pattern does not reflect whitespace, currency, decimals, or locale formatting. | Inspect the actual text and escape regex metacharacters such as $ and .. |
Performance and reliability guidance
- Scope queries to the smallest meaningful container to reduce ambiguity and DOM searching.
- Prefer one precise query over a broad query followed by fragile index selection.
- Use network aliases or ready selectors for asynchronous screens.
- Keep the numeric form for value assertions and anchored regexes for exact presentation assertions.
- Use stable test attributes when copy, localization, or number formatting is expected to change.
- Do not parse text into a JavaScript number unless the test is specifically validating conversion; the DOM contains rendered text.
Or skip the browser setup
If your goal is to capture a page showing a negative value for documentation, visual review, or an AI workflow, ScreenshotNeo returns a screenshot or PDF from one request. Cookie banners, newsletter popups, and chat widgets are removed before the shot; bot checks, blank pages, failed loads, timeouts, and cache hits are not billed. Its MCP server lets AI agents take screenshots, and the free plan includes 1,000 screenshots each month with no card; paid plans start at $5 for 3,000.
See the ScreenshotNeo API documentation for options such as viewport, full-page capture, custom CSS, JavaScript, waiting, headers, cookies, geolocation, caching, and PDF output.
cURL
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://example.com/account \
-o account.webp
Python
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://example.com/account"},
timeout=90,
)
r.raise_for_status()
open("account.webp", "wb").write(r.content)
Node.js
const q = new URLSearchParams({
access_key: 'YOUR_API_KEY',
url: 'https://example.com/account'
})
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`)
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`)
const body = Buffer.from(await res.arrayBuffer())
require('fs').writeFileSync('account.webp', body)
Create a free ScreenshotNeo account with 1,000 screenshots per month and no credit card.
FAQ
Can I pass a negative number directly?
Yes. cy.contains(-42) uses the documented numeric content form.
What is the safest exact match?
Use an anchored regex such as cy.contains(/^-42$/), adjusted for the format your page renders.
Why does a string match more than I expected?
String content is matched as a substring. Use anchors, a selector, and a scoped container when exactness matters.
Should every number be located with text?
No. If the number is incidental to the behavior, a stable data attribute is usually more durable.


