# no-long-text-match
Prevent exact matches on long text that may change due to localization, spacing, or content updates.
## Rule Details
Exact text matching on long strings is brittle and can lead to flaky tests:
- Content may change due to copywriting updates
- Localization can alter text length and structure
- Dynamic content may include variable parts (dates, names, numbers)
- Whitespace handling can be inconsistent across browsers
- Text wrapping and formatting can affect rendered content
This rule helps prevent test flakiness by detecting overly specific text matching that could break with minor content changes.
## Options
This rule accepts an options object with the following properties:
```json
{
"test-flakiness/no-long-text-match": [
"error",
{
"maxLength": 50,
"allowPartialMatch": true,
"ignoreTestIds": false
}
]
}
```
### `maxLength` (default: `50`)
Maximum allowed length for exact text matching.
```javascript
// With maxLength: 30
expect(screen.getByText("Click here")).toBeInTheDocument(); // Allowed (10 chars)
expect(
screen.getByText("This is a very long text that might change"),
).toBeInTheDocument(); // Not allowed (43 chars)
// With maxLength: 50
expect(screen.getByText("This is a moderately long text")).toBeInTheDocument(); // Allowed (31 chars)
```
### `allowPartialMatch` (default: `true`)
When set to `true`, allows partial text matching with regex or substring queries.
```javascript
// With allowPartialMatch: true (default)
expect(screen.getByText(/Click here/)).toBeInTheDocument(); // Allowed
expect(screen.getByText("Long text", { exact: false })).toBeInTheDocument(); // Allowed
// With allowPartialMatch: false
expect(screen.getByText(/Click here/)).toBeInTheDocument(); // Not allowed
```
### `ignoreTestIds` (default: `false`)
When set to `true`, ignores text matching when using `data-testid` attributes.
```javascript
// With ignoreTestIds: true
expect(screen.getByTestId("long-text-element")).toHaveTextContent(
"Very long text",
); // Allowed
// With ignoreTestIds: false (default)
expect(screen.getByTestId("element")).toHaveTextContent("Very long text"); // Not allowed
```
## Examples
### Incorrect
```javascript
// Long exact text matches
expect(
screen.getByText(
"Welcome to our amazing application! Please click the button below to get started.",
),
).toBeInTheDocument();
expect(
screen.getByText(
"Error: The username you entered is already taken. Please try a different username.",
),
).toBeVisible();
// Long text in assertions
expect(element).toHaveTextContent(
"This is a very long paragraph that contains multiple sentences and might change over time due to content updates or localization.",
);
// Cypress long text matches
cy.contains(
"Your account has been successfully created and you will receive a confirmation email shortly.",
).should("be.visible");
// Playwright long text
await expect(
page.getByText(
"Welcome to the dashboard! Here you can manage your account settings and view your recent activity.",
),
).toBeVisible();
// Multi-line text matching
expect(
screen.getByText(`
Line 1: This is the first line of text
Line 2: This is the second line with more content
Line 3: Final line with additional information
`),
).toBeInTheDocument();
// Dynamic content with exact match
expect(
screen.getByText(
`Hello John Doe, your order #12345 has been processed and will be delivered on January 15, 2024.`,
),
).toBeVisible();
// Error message exact matches
expect(
screen.getByText(
"Validation failed: Email address is required, password must be at least 8 characters long, and terms must be accepted.",
),
).toBeInTheDocument();
```
### Correct
```javascript
// Use partial text matching
expect(
screen.getByText(/Welcome to our amazing application/),
).toBeInTheDocument();
expect(screen.getByText("Welcome", { exact: false })).toBeInTheDocument();
// Use more specific selectors
expect(screen.getByRole("alert")).toHaveTextContent(/username.*already taken/i);
// Break down long text into key parts
expect(screen.getByText(/account.*successfully created/i)).toBeVisible();
expect(screen.getByText(/confirmation email/i)).toBeVisible();
// Use ARIA labels or roles
expect(screen.getByRole("heading", { name: /dashboard/i })).toBeVisible();
expect(screen.getByLabelText(/account settings/i)).toBeInTheDocument();
// Test by key phrases
expect(screen.getByText(/order.*processed/)).toBeVisible();
expect(screen.getByText(/delivered on/)).toBeVisible();
// Use data attributes for complex content
Long welcome text...
;
expect(screen.getByTestId("welcome-message")).toBeInTheDocument();
// Focus on semantic meaning
expect(screen.getByRole("status")).toHaveTextContent(/validation failed/i);
expect(screen.getByRole("status")).toHaveTextContent(/email.*required/i);
// Cypress partial matching
cy.contains("account.*created").should("be.visible");
cy.get('[data-cy="success-message"]').should("contain", "successfully");
// Playwright partial matching
await expect(page.locator('[data-testid="welcome"]')).toContainText("Welcome");
await expect(page.getByRole("alert")).toContainText(/error.*username/i);
// Test structure rather than exact content
const paragraph = screen.getByTestId("description");
expect(paragraph).toBeInTheDocument();
expect(paragraph.textContent.length).toBeGreaterThan(100);
// Use contains for key phrases
expect(screen.getByText(/click.*button.*get started/i)).toBeInTheDocument();
```
## Best Practices
### 1. Use Partial Text Matching
Focus on key parts of the text rather than exact matches:
```javascript
// Instead of exact long text
expect(
screen.getByText("Welcome to our application! Click here to continue."),
).toBeInTheDocument();
// Use partial matching
expect(screen.getByText(/welcome.*application/i)).toBeInTheDocument();
expect(screen.getByText("Welcome", { exact: false })).toBeInTheDocument();
```
### 2. Test by Semantic Meaning
Use ARIA roles and semantic selectors:
```javascript
// Instead of long text content
expect(
screen.getByText("Error: Something went wrong with your request"),
).toBeVisible();
// Use semantic roles
expect(screen.getByRole("alert")).toContainText(/error.*request/i);
```
### 3. Break Down Complex Assertions
Test different parts of long content separately:
```javascript
// Instead of one long assertion
expect(element).toHaveTextContent(
"User John Doe logged in at 2024-01-15 14:30:25 from IP 192.168.1.1",
);
// Break into logical parts
expect(element).toHaveTextContent(/John Doe/);
expect(element).toHaveTextContent(/logged in/);
expect(element).toHaveTextContent(/2024-01-15/);
```
### 4. Use Data Attributes for Complex Content
Use `data-testid` for elements with complex or variable text:
```javascript
// Markup
Long user information with variable content...
;
// Test
const userInfo = screen.getByTestId("user-info");
expect(userInfo).toBeInTheDocument();
expect(userInfo).toHaveTextContent(/user information/i);
```
### 5. Focus on User Actions and Outcomes
Test what users see and do rather than exact text:
```javascript
// Instead of exact error text
expect(
screen.getByText(
"Form validation failed: Username is required and must be at least 3 characters long",
),
).toBeVisible();
// Test the effect on user workflow
const usernameField = screen.getByLabelText(/username/i);
expect(usernameField).toBeInvalid();
expect(screen.getByRole("alert")).toBeInTheDocument();
```
### 6. Handle Localized Content
Prepare tests for internationalization:
```javascript
// Instead of hardcoded text
expect(screen.getByText("Submit Form")).toBeInTheDocument();
// Use ARIA labels that work across languages
expect(screen.getByRole("button", { name: /submit/i })).toBeInTheDocument();
// Or use data attributes
expect(screen.getByTestId("submit-button")).toBeInTheDocument();
```
## Framework-Specific Examples
### React Testing Library
```javascript
// ❌ Long exact matches
expect(
screen.getByText("Welcome to React Testing Library example application"),
).toBeInTheDocument();
// ✅ Partial matching strategies
expect(screen.getByText(/welcome.*react/i)).toBeInTheDocument();
expect(screen.getByRole("heading")).toHaveTextContent(/welcome/i);
expect(screen.getByText("Welcome", { exact: false })).toBeInTheDocument();
```
### Cypress
```javascript
// ❌ Long text matching
cy.contains("This is a very long error message that might change").should(
"be.visible",
);
// ✅ Partial matching
cy.contains(/error.*message/i).should("be.visible");
cy.get('[data-cy="error"]').should("contain", "error");
```
### Playwright
```javascript
// ❌ Exact long text
await expect(
page.getByText("Long descriptive text that might change"),
).toBeVisible();
// ✅ Partial matching
await expect(page.getByText(/descriptive.*text/i)).toBeVisible();
await expect(page.locator('[data-testid="description"]')).toContainText(
"descriptive",
);
```
## Common Patterns to Avoid
### Error Messages
```javascript
// ❌ Exact error text
expect(
screen.getByText(
"Validation error: Email is required, password must be 8+ characters",
),
).toBeVisible();
// ✅ Key error indicators
expect(screen.getByRole("alert")).toHaveTextContent(/validation error/i);
expect(screen.getByText(/email.*required/i)).toBeVisible();
```
### Success Messages
```javascript
// ❌ Full success text
cy.contains(
"Your profile has been successfully updated and changes will be visible shortly",
).should("be.visible");
// ✅ Success indicators
cy.get('[role="status"]').should("contain", "successfully updated");
cy.contains(/profile.*updated/i).should("be.visible");
```
### Instructions and Help Text
```javascript
// ❌ Complete instructions
expect(
screen.getByText(
"To get started, please fill out the form below and click submit when ready",
),
).toBeInTheDocument();
// ✅ Key instruction phrases
expect(screen.getByText(/get started/i)).toBeInTheDocument();
expect(screen.getByText(/fill out.*form/i)).toBeInTheDocument();
```
## When Not To Use It
This rule may not be suitable if:
- You're testing specific copywriting or content accuracy
- You need to verify exact error message formatting
- You're working with fixed, unchangeable text content
- You're testing text processing or formatting functions
In these cases:
```javascript
// Disable for specific content verification
// eslint-disable-next-line test-flakiness/no-long-text-match
expect(screen.getByText('Exact legal disclaimer text')).toBeInTheDocument();
// Or adjust the configuration
{
"test-flakiness/no-long-text-match": ["error", {
"maxLength": 100,
"allowPartialMatch": true
}]
}
```
## Related Rules
- [no-index-queries](./no-index-queries.md) - Prevents position-dependent queries
- [no-viewport-dependent](./no-viewport-dependent.md) - Prevents viewport-dependent assertions
- [no-immediate-assertions](./no-immediate-assertions.md) - Requires proper waiting for assertions
## Further Reading
- [Testing Library - TextMatch](https://testing-library.com/docs/queries/about#textmatch)
- [Testing Library - Queries](https://testing-library.com/docs/queries/about)
- [Internationalization Testing Strategies](https://kentcdodds.com/blog/effective-snapshot-testing)
- [Cypress - Text Content](https://docs.cypress.io/guides/references/best-practices#Selecting-Elements)
- [Playwright - Text Selectors](https://playwright.dev/docs/locators#text-selectors)