Skip to content

#272: created the Javascript trait for tracking js errors on the page #418

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

nsineok
Copy link
Contributor

@nsineok nsineok commented May 27, 2025

#272

Summary by CodeRabbit

  • New Features
    • Introduced automated detection and reporting of JavaScript errors during Behat test runs, with assertions available at both step and scenario levels.
  • Tests
    • Added new tests to verify that visiting key pages does not produce JavaScript errors.

Copy link

coderabbitai bot commented May 27, 2025

Walkthrough

A new PHP class, JavascriptErrorContext, has been introduced to enhance Behat tests by capturing and asserting JavaScript errors during browser sessions. The class integrates with Behat's lifecycle events, injects JavaScript to monitor errors, and provides assertion steps. Additionally, a new Behat feature file tests the absence of JavaScript errors on specific pages.

Changes

File(s) Change Summary
src/JavascriptErrorContext.php Added a new context class to capture, aggregate, and assert JavaScript errors during Behat test runs.
tests/behat/features/js_error.feature Introduced a feature file to test and assert the absence of JavaScript errors on visited pages.

Poem

In Behat’s warren, scripts now hop,
Catching errors that once would drop.
With every page and every run,
JavaScript bugs are on the run!
Rabbits cheer with every test,
For code that’s clean is simply best.
🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@nsineok nsineok marked this pull request as ready for review May 27, 2025 06:15
@nsineok nsineok added the State: In progress The issue is being worked on label May 27, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

♻️ Duplicate comments (2)
src/JavascriptErrorContext.php (2)

58-74: Likewise, mark afterStep() as a Behat hook

Add @AfterStep so the collection logic is actually triggered.

-  public function afterStep(AfterStepScope $scope): void {
+  /**
+   * @AfterStep
+   */
+  public function afterStep(AfterStepScope $scope): void {

95-108: Scenario hook also needs registration

Same reasoning – add the hook annotation/attribute:

-  public function afterScenario(AfterScenarioScope $scope): void {
+  /**
+   * @AfterScenario
+   */
+  public function afterScenario(AfterScenarioScope $scope): void {
📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9f2fa7b and 7f810b5.

📒 Files selected for processing (2)
  • src/JavascriptErrorContext.php (1 hunks)
  • tests/behat/features/js_error.feature (1 hunks)
🧰 Additional context used
🪛 PHPStan (2.1.15)
src/JavascriptErrorContext.php

21-21: Property DrevOps\BehatSteps\JavascriptErrorContext::$jsErrorRegistry type has no value type specified in iterable type array.
See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type

(missingType.iterableValue)


26-26: Property DrevOps\BehatSteps\JavascriptErrorContext::$stepFilters type has no value type specified in iterable type array.
See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type

(missingType.iterableValue)

Comment on lines +35 to +52
public function beforeStep(BeforeStepScope $scope): void {
$stepText = $scope->getStep()->getText();
if ($this->shouldCaptureForStep($stepText)) {
$script = <<<JS
if (!window.__behatErrors) {
window.__behatErrors = [];
(function() {
const originalError = console.error;
console.error = function(...args) {
window.__behatErrors.push(args.join(" "));
originalError.apply(console, args);
};
})();
}
JS;
$this->getSession()->executeScript($script);
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

⚠️ Potential issue

Behat hooks will not execute – missing @BeforeStep annotation/attribute

beforeStep() is intended to run as a Behat hook, but Behat only registers hooks that are explicitly marked with either a PHP-8 attribute (#[BeforeStep]) or a doc-block annotation (@BeforeStep).
Without one of those markers the method is treated as an ordinary helper and will never be called, so no script is injected and no errors are captured.

-  public function beforeStep(BeforeStepScope $scope): void {
+  /**
+   * @BeforeStep
+   */
+  public function beforeStep(BeforeStepScope $scope): void {

(Repeat the same fix with @AfterStep for afterStep() and @AfterScenario for afterScenario().)

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public function beforeStep(BeforeStepScope $scope): void {
$stepText = $scope->getStep()->getText();
if ($this->shouldCaptureForStep($stepText)) {
$script = <<<JS
if (!window.__behatErrors) {
window.__behatErrors = [];
(function() {
const originalError = console.error;
console.error = function(...args) {
window.__behatErrors.push(args.join(" "));
originalError.apply(console, args);
};
})();
}
JS;
$this->getSession()->executeScript($script);
}
}
/**
* @BeforeStep
*/
public function beforeStep(BeforeStepScope $scope): void {
$stepText = $scope->getStep()->getText();
if ($this->shouldCaptureForStep($stepText)) {
$script = <<<JS
if (!window.__behatErrors) {
window.__behatErrors = [];
(function() {
const originalError = console.error;
console.error = function(...args) {
window.__behatErrors.push(args.join(" "));
originalError.apply(console, args);
};
})();
}
JS;
$this->getSession()->executeScript($script);
}
}
🤖 Prompt for AI Agents
In src/JavascriptErrorContext.php around lines 35 to 52, the beforeStep() method
lacks the necessary Behat hook annotation or attribute, so it will not be
executed as a hook. Add the PHP-8 attribute #[BeforeStep] or the doc-block
annotation @BeforeStep immediately above the beforeStep() method declaration.
Similarly, add #[AfterStep] or @AfterStep above afterStep() and #[AfterScenario]
or @AfterScenario above afterScenario() to ensure these methods are registered
and executed as Behat hooks.

Comment on lines +18 to +29
/**
* Errors registry keyed by path.
*/
private array $jsErrorRegistry = [];

/**
* Step patterns.
*/
private array $stepFilters = [
'/^I (visit|go to)/',
];

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Silence PHPStan “no value type specified” warnings

Add generics in doc-blocks so static analysis knows the structure of the arrays:

-  private array $jsErrorRegistry = [];
+  /** @var array<string, string[]> */
+  private array $jsErrorRegistry = [];

-  private array $stepFilters = [
+  /** @var array<int, string> */
+  private array $stepFilters = [

Purely cosmetic but keeps PHPStan quiet and improves IDE tooling.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* Errors registry keyed by path.
*/
private array $jsErrorRegistry = [];
/**
* Step patterns.
*/
private array $stepFilters = [
'/^I (visit|go to)/',
];
/**
* Errors registry keyed by path.
*/
/** @var array<string, string[]> */
private array $jsErrorRegistry = [];
/**
* Step patterns.
*/
/** @var array<int, string> */
private array $stepFilters = [
'/^I (visit|go to)/',
];
🧰 Tools
🪛 PHPStan (2.1.15)

21-21: Property DrevOps\BehatSteps\JavascriptErrorContext::$jsErrorRegistry type has no value type specified in iterable type array.
See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type

(missingType.iterableValue)


26-26: Property DrevOps\BehatSteps\JavascriptErrorContext::$stepFilters type has no value type specified in iterable type array.
See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type

(missingType.iterableValue)

🤖 Prompt for AI Agents
In src/JavascriptErrorContext.php around lines 18 to 29, the private arrays
$jsErrorRegistry and $stepFilters lack generic type annotations in their
doc-blocks, causing PHPStan to emit "no value type specified" warnings. Add
appropriate generic type annotations in the doc-block comments above these
properties to specify the expected types of the array keys and values, which
will silence PHPStan warnings and improve IDE support.

Comment on lines +26 to +29
private array $stepFilters = [
'/^I (visit|go to)/',
];

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Navigation pattern "I am on" isn’t captured

The first step in your feature uses “Given I am on …”, yet stepFilters only checks for "I visit" and "I go to".
Consequently the very first page load skips JS-error tracking.

 private array $stepFilters = [
     '/^I (visit|go to)/',
+    '/^I am on/',
 ];

Consider also exposing the default filters as constants or allowing configuration so projects with different vocabularies don’t have to fork the class.


I’ll verify the patterns and class configuration support by inspecting the stepFilters definition and related methods.


🏁 Script executed:

#!/bin/bash
# Locate the file
file=$(fd JavascriptErrorContext.php)
echo "Inspecting: $file"

# Show where stepFilters is declared
grep -n "stepFilters" -n "$file"

# Display the first 200 lines to review definition and constructor
sed -n '1,200p' "$file"

Length of output: 3779


Add missing step filter for “I am on” navigation pattern

The stepFilters array currently only matches I visit and I go to, so steps like “I am on …” aren’t captured and the initial page load skips JS-error tracking. Update the filter definition as follows:

 private array $stepFilters = [
-    '/^I (visit|go to)/',
+    '/^I (visit|go to)/',
+    '/^I am on/',
 ];

Additional recommendations:

  • Expose the default filters as a class constant or accept them via constructor/configuration to avoid forking when projects use different step vocabularies.
  • Projects can still add custom patterns at runtime using addStepFilter(), but making the defaults configurable improves flexibility.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private array $stepFilters = [
'/^I (visit|go to)/',
];
private array $stepFilters = [
'/^I (visit|go to)/',
'/^I am on/',
];
🧰 Tools
🪛 PHPStan (2.1.15)

26-26: Property DrevOps\BehatSteps\JavascriptErrorContext::$stepFilters type has no value type specified in iterable type array.
See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type

(missingType.iterableValue)

🤖 Prompt for AI Agents
In src/JavascriptErrorContext.php around lines 26 to 29, the stepFilters array
only matches "I visit" and "I go to" patterns, missing the "I am on" navigation
phrase, causing initial page loads to skip JS-error tracking. Update the
stepFilters array to include a pattern matching "I am on" steps. Additionally,
refactor the class to expose the default filters as a class constant or accept
them via constructor or configuration, allowing projects to customize or extend
step filters without forking the class.

Comment on lines +38 to +49
$script = <<<JS
if (!window.__behatErrors) {
window.__behatErrors = [];
(function() {
const originalError = console.error;
console.error = function(...args) {
window.__behatErrors.push(args.join(" "));
originalError.apply(console, args);
};
})();
}
JS;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Injected logger is wiped out by page reloads

console.error is overridden before the navigation step executes.
When the browser navigates, the page context (and thus the override) is lost, meaning errors on the new page won’t be recorded.

Two possible fixes:

  1. Inject the logger after the navigation completes (inside afterStep() before you read the buffer for the next step).
  2. Re-inject the script immediately after you clear the buffer in afterStep().

Please verify the current behaviour with a page that throws a deliberate console.error on load; you’ll see it is not captured.


🏁 Script executed:

#!/usr/bin/env bash
set -eux

# Locate the JavascriptErrorContext file
file=$(fd JavascriptErrorContext.php)

# Display the file path
echo "Found file: $file"

# Show the first 150 lines for context
sed -n '1,150p' "$file"

# Search for hook annotations to see when injection happens
rg -n '@BeforeStep|@BeforeScenario|@AfterStep' "$file" || true

Length of output: 3693


Ensure the JS error logger is re-injected after each navigation

Overriding console.error in beforeStep() happens before the visit step runs, so the override is lost when the browser reloads into the new page context. You must inject (or re-inject) the logger after navigation completes.

Files to update:

  • src/JavascriptErrorContext.php
    • beforeStep() is too early for navigation steps.
    • afterStep() needs to re-inject the override for navigation steps.

Suggested change in afterStep() (inside the if ($this->shouldCaptureForStep(...)) block):

   // Clear the error buffer to avoid duplicate capture
-  $this->getSession()->executeScript('window.__behatErrors = [];');
+  $this->getSession()->executeScript('window.__behatErrors = [];');
+
+  // Re-inject the error logger into the new page context
+  $this->getSession()->executeScript(<<<'JS'
+    // Initialize buffer and override console.error
+    window.__behatErrors = [];
+    (function() {
+      const originalError = console.error;
+      console.error = function(...args) {
+        window.__behatErrors.push(args.join(" "));
+        originalError.apply(console, args);
+      };
+    })();
+  JS
+  );

After applying, verify by visiting a page that calls console.error on load—you should now capture that error.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/JavascriptErrorContext.php between lines 38 and 49, the console.error
override is injected in beforeStep(), which is too early because it gets wiped
out by page reloads during navigation steps. To fix this, move the injection
logic so that it runs in afterStep() instead, specifically re-inject the
console.error override after navigation steps complete inside the if
($this->shouldCaptureForStep(...)) block. This ensures the logger is active in
the new page context and captures errors on page load.

Comment on lines +5 to +6
Given I am on "/"
Then I should see no JavaScript errors
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Step wording not tracked by default filters

Because the context currently only listens for steps starting with “I visit” or “I go to”, the “Given I am on” navigation step will not trigger JS-error logging.
Either refactor the context (preferred – see previous comment) or change the step here to “Given I visit "/"”.


🏁 Script executed:

#!/bin/bash
# Search for step definitions in Behat contexts
rg -n "@(Given|When|Then)" -C2 --path-separator / .

Length of output: 73022


🏁 Script executed:

#!/bin/bash
# Locate step definition for JS error logging in the Behat contexts
rg -n "JavaScript errors" -C2 --path-separator / tests/behat/bootstrap

Length of output: 72


🏁 Script executed:

#!/bin/bash
# Search for the step definition of "Then I should see no JavaScript errors" across the repo.
rg -n "I should see no JavaScript errors" -C2 --path-separator /

Length of output: 807


🏁 Script executed:

#!/bin/bash
# Display the beginning of src/JavascriptErrorContext.php to inspect navigation step filtering
sed -n '1,200p' src/JavascriptErrorContext.php

Length of output: 3429


Add “I am on” to JS‐error capture or update the feature step

By default JavascriptErrorContext::$stepFilters only matches I visit or I go to, so your “Given I am on "/"” step never instruments the page. You can either:

  • Update the context’s filter to also match “I am on” (in src/JavascriptErrorContext.php):
    private array $stepFilters = [
      '/^I (visit|go to|am on)/',
    ];
  • Or change your feature to use the existing filters (in tests/behat/features/js_error.feature):
       Scenario: Visiting home page should not produce JS errors
  • Given I am on "/"
  • Given I visit "/"
    Then I should see no JavaScript errors

<details>
<summary>🤖 Prompt for AI Agents</summary>

In tests/behat/features/js_error.feature around lines 5 to 6, the step "Given I
am on "/"" does not trigger JavaScript error logging because the context only
listens for steps starting with "I visit" or "I go to". To fix this, either
update the stepFilters array in src/JavascriptErrorContext.php to include "I am
on" by adding it to the regex pattern, or change the feature step here to "Given
I visit "/"" to match the existing filters.


</details>

<!-- This is an auto-generated comment by CodeRabbit -->

@AlexSkrypnyk AlexSkrypnyk self-assigned this May 31, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
State: In progress The issue is being worked on
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants