Skip to content

feat: add assertion#19

Merged
kriptonian1 merged 14 commits intodevelopfrom
feat/add-assertion
Jan 17, 2026
Merged

feat: add assertion#19
kriptonian1 merged 14 commits intodevelopfrom
feat/add-assertion

Conversation

@kriptonian1
Copy link
Copy Markdown
Owner

@kriptonian1 kriptonian1 commented Jan 9, 2026

User description

Description

This PR adds the assertion behavior to symphony, this will help the end user to assert is a value or element is present or not

Fixes #17

Dependencies

Mention any dependencies/packages used

Future Improvements

Mention any improvements to be done in future related to any file/feature

Mentions

Mention and tag the people

Screenshots of relevant screens

Add screenshots of relevant screens

Developer's checklist

  • My PR follows the style guidelines of this project
  • I have performed a self-check on my work

If changes are made in the code:

  • I have followed the coding guidelines
  • My changes in code generate no new warnings
  • My changes are breaking another fix/feature of the project
  • I have added test cases to show that my feature works
  • I have added relevant screenshots in my PR
  • There are no UI/UX issues

PR Type

Enhancement


Description

  • Add five assertion actions: isVisible, isTitle, isURL, isNotVisible, isDisabled

  • Support both text content and CSS selector targeting for element assertions

  • Support regex patterns for title and URL assertions

  • Create FailedAssertionError for assertion failure handling

  • Refactor scroll flow parameters and extract distance calculation logic

  • Update pre-commit hook to fail on lint warnings

  • Auto-generate JSON schema with comprehensive assertion documentation


Diagram Walkthrough

flowchart LR
  A["Workflow Config"] -->|Parse| B["FlowStepSchema"]
  B -->|Route| C["flowRegistry"]
  C -->|Execute| D["Assertion Flows"]
  D -->|Check| E["Element/Page State"]
  E -->|Pass| F["Continue Workflow"]
  E -->|Fail| G["FailedAssertionError"]
  G -->|Handle| H["Error Handler"]
Loading

File Walkthrough

Relevant files
Documentation
3 files
json-schema-meta.ts
Add metadata for five assertion actions                                   
+93/-0   
symphony.json
Auto-generate JSON schema for assertions                                 
+126/-0 
tailwind.yml
Add assertion examples to workflow demo                                   
+9/-0     
Error handling
2 files
workflow-error.ts
Add FailedAssertionError exception class                                 
+7/-0     
handle-workflow-error.ts
Handle FailedAssertionError in error handler                         
+3/-0     
Enhancement
11 files
isvisible-flow.ts
Implement isVisible assertion flow                                             
+41/-0   
istitle-flow.ts
Implement isTitle assertion flow                                                 
+34/-0   
isurl-flow.ts
Implement isURL assertion flow                                                     
+32/-0   
is-not-visible-flow.ts
Implement isNotVisible assertion flow                                       
+41/-0   
is-disabled-flow.ts
Implement isDisabled assertion flow                                           
+40/-0   
index.ts
Export new assertion flows with consistent syntax               
+10/-7   
scroll-flow.ts
Refactor scroll parameters and extract distance logic       
+19/-5   
flow-registry.ts
Register five new assertion flows in registry                       
+14/-4   
index.ts
Use chalk for colored error logging                                           
+5/-1     
workflow-config.types.ts
Add schemas and types for assertion actions                           
+55/-0   
regex-or-string-maker.ts
Create utility to parse regex or string patterns                 
+23/-0   
Configuration changes
2 files
pre-commit
Update pre-commit hook to fail on warnings                             
+1/-1     
tsconfig.json
Add utils path alias to TypeScript config                               
+2/-1     
Formatting
1 files
package.json
Reorder dependencies alphabetically                                           
+2/-2     

@kriptonian1 kriptonian1 marked this pull request as draft January 9, 2026 20:28
@kriptonian1
Copy link
Copy Markdown
Owner Author

/review

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review bot commented Jan 15, 2026

PR Reviewer Guide 🔍

(Review updated until commit 8411b4f)

Here are some key observations to aid the review process:

🎫 Ticket compliance analysis 🔶

17 - Partially compliant

Compliant requirements:

  • Add assertion support so users can verify presence/conditions in workflows.
  • assert boolean: pass if a certain element exists on screen.

Non-compliant requirements:

  • assert equal: pass a conditional check for an element value (example: assert: #element.value >= 10).
  • assert not equal: inverse of assert equal.

Requires further human verification:

  • Validate assertion steps behavior against real pages (timing/auto-wait, dynamic elements, false positives with text locators).
  • Validate JSON schema / editor integration renders the new jsonSchemaMeta markdown as intended.
⏱️ Estimated effort to review: 4 🔵🔵🔵🔵⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Bug Risk

Regex parsing logic only extracts the first segment between slashes via str.split("/")[1], which breaks patterns containing additional slashes and ignores regex flags. It may also create unexpected regexes for inputs like /a\/b/ or /foo/i. Consider a safer parser (e.g., last-slash split + flags) and/or explicit schema support for regex instead of encoding it in a string.

if (str.startsWith("/") && str.endsWith("/")) {
	const newRegex = new RegExp(str.split("/")[1]);
	return { isRegex: true, value: newRegex };
}
Incorrect Messaging

Spinner variable and failure message text refer to isTitle / "visibility of page URL", which is confusing for users and makes debugging workflows harder. Align spinner naming and messages to isURL, and prefer "matches URL" rather than "visible".

const isTitleSpinner = spinner();

const { isRegex, value: titleOrRegex } = regexOrStringMaker(isURLStep.isURL);

const LocatorDescription = isRegex ? "Regex:" : "URL:";

isTitleSpinner.start(`Checking visibility of page URL ${titleOrRegex}`);

try {
	await expect(page).toHaveURL(titleOrRegex);
	isTitleSpinner.stop(
		`${chalk.green("✓")} Page URL is visible with ${LocatorDescription} "${titleOrRegex}"`,
	);
} catch {
	isTitleSpinner.stop(
		`${chalk.red("✖")} isTitle: Page URL is not visible with ${LocatorDescription} "${titleOrRegex}"`,
	);
Wrong Description

The spinner start message says "Checking visibility" even though the action checks disabled state. Similar wording appears in other assertion flows (title/URL also talk about "visibility"). This can mislead users when an assertion fails; update messages to describe the actual check (disabled/hidden/title match/URL match).

isDisabledSpinner.start(
	`Checking visibility of element with ${targetDescription}`,
);

try {
	await expect(targetLocator).toBeDisabled();
	isDisabledSpinner.stop(
		`${chalk.green("✓")} Element is disabled with ${targetDescription}`,
	);
} catch {
	isDisabledSpinner.stop(
		`${chalk.red("✖")} isDisabled: Element is not disabled with ${targetDescription}`,
	);
	throw new FailedAssertionError(`Assertion failed: ${targetDescription}`);

@kriptonian1 kriptonian1 self-assigned this Jan 15, 2026
@sonarqubecloud
Copy link
Copy Markdown

@kriptonian1 kriptonian1 changed the title [WIP] feat: add assertion feat: add assertion Jan 17, 2026
@kriptonian1 kriptonian1 marked this pull request as ready for review January 17, 2026 12:11
@kriptonian1
Copy link
Copy Markdown
Owner Author

/review

@qodo-code-review
Copy link
Copy Markdown

Persistent review updated to latest commit 8411b4f

@qodo-code-review
Copy link
Copy Markdown

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
Regex ReDoS risk

Description: User-controlled regex strings are converted to RegExp via new RegExp(...) and then used in
Playwright matchers (e.g., title/URL assertions), which could enable catastrophic
backtracking (ReDoS-like) and cause hangs/timeouts when given malicious patterns (e.g.,
/(a+)+$/). regex-or-string-maker.ts [15-22]

Referred Code
export default function regexOrStringMaker(
	str: string,
): RegexOrStringMakerValue {
	if (str.startsWith("/") && str.endsWith("/")) {
		const newRegex = new RegExp(str.split("/")[1]);
		return { isRegex: true, value: newRegex };
	}
	return { isRegex: false, value: str };
Ticket Compliance
🟡
🎫 #17
🟢 Provide an "assert boolean" capability that passes when a specified element exists on the
screen.
🔴 Provide an "assert equal" capability to pass conditional checks on an element value
(example: assert: #element.value >= 10).
Provide an "assert not equal" capability that is the inverse of "assert equal".
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

🔴
Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status:
Misleading identifiers: New code introduces inconsistent/incorrect naming (e.g., LocatorDescription is not
camelCase and spinner/message labels like isTitleSpinner are reused for URL checks),
reducing self-documentation and increasing confusion.

Referred Code
const isTitleSpinner = spinner();

const { isRegex, value: titleOrRegex } = regexOrStringMaker(
	isTitleStep.isTitle,
);

const LocatorDescription = isRegex ? "Regex:" : "Text:";

isTitleSpinner.start(`Checking visibility of page title ${titleOrRegex}`);

try {
	await expect(page).toHaveTitle(titleOrRegex);

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Unsafe regex parsing: The regex parsing logic constructs RegExp from user-provided strings without handling
flags, escaped slashes, empty patterns, or invalid regex errors, which can cause
unexpected runtime failures without actionable context.

Referred Code
export default function regexOrStringMaker(
	str: string,
): RegexOrStringMakerValue {
	if (str.startsWith("/") && str.endsWith("/")) {
		const newRegex = new RegExp(str.split("/")[1]);
		return { isRegex: true, value: newRegex };
	}
	return { isRegex: false, value: str };

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status:
Possible stack exposure: The generic error handler prints the raw error object via console.error("Workflow
failed:", error), which may expose stack traces or internal details to end users
depending on runtime/error content.

Referred Code
log.error(chalk.red("An unexpected error occurred."));
console.error("Workflow failed:", error);
outro(chalk.red("✖ Workflow failed unexpectedly."));

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status:
Unstructured console logging: The scroll flow introduces direct console.log output (e.g., logging scroll positions)
which is unstructured and may unintentionally log sensitive runtime context depending on
workflow inputs.

Referred Code
const { x, y } = scrollStep.scroll.position;
console.log(`Scrolling to position: (${x}, ${y})`);
await page.mouse.move(x, y);

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status:
Unsanitized user selectors: Workflow-provided selector/text inputs are passed directly into Playwright locator APIs
and echoed into error messages, which may be acceptable for a CLI tool but could require
sanitization/redaction depending on expected threat model and input sources.

Referred Code
if (typeof isVisibleStep.isVisible === "string") {
	targetLocator = page.getByText(isVisibleStep.isVisible).first();
	targetDescription = `text: ${isVisibleStep.isVisible}`;
} else {
	targetLocator = page.locator(isVisibleStep.isVisible.selector);
	targetDescription = `selector: ${isVisibleStep.isVisible.selector}`;
}

isVisibleSpiner.start(
	`Checking visibility of element with ${targetDescription}`,
);

try {
	await expect(targetLocator).toBeVisible();
	isVisibleSpiner.stop(
		`${chalk.green("✓")} Element is visible with ${targetDescription}`,
	);
} catch {
	isVisibleSpiner.stop(
		`${chalk.red("✖")} isVisible: Element is not visible with ${targetDescription}`,
	);


 ... (clipped 2 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-code-review
Copy link
Copy Markdown

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Unify assertion logic into a single, configurable action

Refactor the five new, separate assertion actions into a single, configurable
assert action. This will eliminate significant code duplication and improve the
system's maintainability and extensibility.

Examples:

src/execution-flow/flows/isvisible-flow.ts [9-41]
export default async function isVisibleFlow({
	step: isVisibleStep,
	page,
}: BaseFlowParam<IsVisibleAction>): Promise<void> {
	const isVisibleSpiner = spinner();

	let targetLocator: Locator;
	let targetDescription: `text: ${string}` | `selector: ${string}`;

	if (typeof isVisibleStep.isVisible === "string") {

 ... (clipped 23 lines)
src/execution-flow/flows/is-not-visible-flow.ts [9-41]
export default async function isNotVisibleFlow({
	step: isNotVisibleStep,
	page,
}: BaseFlowParam<IsNotVisibleAction>) {
	const isNotVisibleSpiner = spinner();

	let targetLocator: Locator;
	let targetDescription: `text: ${string}` | `selector: ${string}`;

	if (typeof isNotVisibleStep.isNotVisible === "string") {

 ... (clipped 23 lines)

Solution Walkthrough:

Before:

// src/execution-flow/flows/isvisible-flow.ts
export default async function isVisibleFlow({ step, page }) {
  // ... logic to get locator from string or selector
  const targetLocator = page.locator(step.isVisible.selector);
  try {
    await expect(targetLocator).toBeVisible();
    // ... success logging
  } catch {
    // ... error logging and throwing FailedAssertionError
  }
}

// src/execution-flow/flows/is-not-visible-flow.ts
export default async function isNotVisibleFlow({ step, page }) {
  // ... almost identical logic to get locator
  const targetLocator = page.locator(step.isNotVisible.selector);
  try {
    await expect(targetLocator).toBeHidden(); // only the expect call changes
    // ... success logging
  } catch {
    // ... error logging and throwing FailedAssertionError
  }
}
// ... similar duplication for isDisabled, isTitle, isURL

After:

// types/workflow-config.types.ts
export const AssertActionSchema = z.object({
  assert: z.object({
    type: z.enum(["isVisible", "isNotVisible", "isDisabled", "isTitle", "isURL"]),
    selector: z.string().optional(),
    text: z.string().optional(),
    value: z.string().optional(), // For title/URL
  })
});

// src/execution-flow/flows/assert-flow.ts
export default async function assertFlow({ step, page }) {
  const { type, selector, text, value } = step.assert;
  // ... logic to get locator from selector or text
  
  try {
    switch (type) {
      case "isVisible": await expect(locator).toBeVisible(); break;
      case "isNotVisible": await expect(locator).toBeHidden(); break;
      case "isDisabled": await expect(locator).toBeDisabled(); break;
      case "isTitle": await expect(page).toHaveTitle(value); break;
      case "isURL": await expect(page).toHaveURL(value); break;
    }
    // ... generic success logging
  } catch {
    // ... generic error logging and throwing FailedAssertionError
  }
}
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies significant code duplication across the new assertion flows and proposes a more scalable and maintainable design by unifying them into a single assert action.

High
Possible issue
Fix incorrect regular expression parsing

Fix a bug in regular expression parsing by using str.slice(1, -1) instead of
str.split("/")[1]. This correctly handles regex patterns that contain forward
slashes.

utils/regex-or-string-maker.ts [18-21]

 if (str.startsWith("/") && str.endsWith("/")) {
-	const newRegex = new RegExp(str.split("/")[1]);
+	const pattern = str.slice(1, -1);
+	const newRegex = new RegExp(pattern);
 	return { isRegex: true, value: newRegex };
 }
  • Apply / Chat
Suggestion importance[1-10]: 9

__

Why: This suggestion correctly identifies a significant bug in the new regexOrStringMaker function where parsing a regex string with internal slashes would fail. The proposed fix using slice is correct and robust, preventing runtime errors for valid inputs.

High
General
Correct spinner naming and messages

In isurl-flow.ts, rename the isTitleSpinner variable to isURLSpinner and correct
the log messages to refer to URL checking instead of title checking.

src/execution-flow/flows/isurl-flow.ts [13-29]

-const isTitleSpinner = spinner();
+const isURLSpinner = spinner();
 ...
-isTitleSpinner.start(`Checking visibility of page URL ${titleOrRegex}`);
+isURLSpinner.start(`Checking page URL ${titleOrRegex}`);
 ...
-isTitleSpinner.stop(
-    `${chalk.red("✖")} isTitle: Page URL is not visible with ${LocatorDescription} "${titleOrRegex}"`,
+isURLSpinner.stop(
+    `${chalk.red("✖")} isURL: Page URL does not match ${LocatorDescription} "${titleOrRegex}"`,
 );

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 4

__

Why: This suggestion correctly identifies and fixes multiple copy-paste errors in the isurl-flow.ts file, including the spinner variable name and its associated messages. This improves code correctness and makes debugging easier.

Low
Correct spinner message for disabled check

Update the spinner message in isDisabledFlow from "Checking visibility of
element" to "Checking disabled state of element" to accurately reflect the
action being performed.

src/execution-flow/flows/is-disabled-flow.ts [25-27]

 isDisabledSpinner.start(
-	`Checking visibility of element with ${targetDescription}`,
+	`Checking disabled state of element with ${targetDescription}`,
 );
  • Apply / Chat
Suggestion importance[1-10]: 3

__

Why: This suggestion correctly identifies a misleading spinner message, which is likely a copy-paste error. The fix improves the clarity of the tool's output for the user, making it easier to follow the execution flow.

Low
Improve spinner message for title check

Improve the spinner message in isTitleFlow from "Checking visibility of page
title" to "Checking page title" for better clarity on the action performed.

src/execution-flow/flows/istitle-flow.ts [21]

-isTitleSpinner.start(`Checking visibility of page title ${titleOrRegex}`);
+isTitleSpinner.start(`Checking page title ${titleOrRegex}`);
  • Apply / Chat
Suggestion importance[1-10]: 1

__

Why: This suggestion offers a minor wording improvement for a spinner message to be more precise. While technically correct, the impact on functionality or readability is minimal.

Low
  • More

@kriptonian1 kriptonian1 merged commit 685bdc1 into develop Jan 17, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FEAT: Support of assertions

2 participants