diff --git a/.changeset/README.md b/.changeset/README.md
new file mode 100644
index 0000000..ecac341
--- /dev/null
+++ b/.changeset/README.md
@@ -0,0 +1,5 @@
+# Changesets
+
+This directory stores release metadata for the monorepo.
+
+Run `npm run changeset` after changing one or more publishable packages, then commit the generated markdown file.
diff --git a/.changeset/config.json b/.changeset/config.json
new file mode 100644
index 0000000..2eada15
--- /dev/null
+++ b/.changeset/config.json
@@ -0,0 +1,11 @@
+{
+ "$schema": "https://unpkg.com/@changesets/config@3.0.4/schema.json",
+ "changelog": "@changesets/cli/changelog",
+ "commit": false,
+ "fixed": [],
+ "linked": [],
+ "access": "public",
+ "baseBranch": "main",
+ "updateInternalDependencies": "patch",
+ "ignore": []
+}
diff --git a/.circleci/config.yml b/.circleci/config.yml
deleted file mode 100644
index 2a7f607..0000000
--- a/.circleci/config.yml
+++ /dev/null
@@ -1,14 +0,0 @@
-# Use the latest 2.1 version of CircleCI pipeline process engine.
-# See: https://circleci.com/docs/2.0/configuration-reference
-version: 2.1
-
-orbs:
- # "cypress-io/cypress@1" installs the latest published
- # version "1.x.y" of the orb. We recommend you then use
- # the strict explicit version "cypress-io/cypress@1.x.y"
- # to lock the version and prevent unexpected CI changes
- cypress: cypress-io/cypress@3
-workflows:
- run_cypress_tests:
- jobs:
- - cypress/run # "run" job comes from "cypress" orb
\ No newline at end of file
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..2d49c9a
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,74 @@
+name: CI
+
+on:
+ pull_request:
+ push:
+ branches:
+ - main
+
+jobs:
+ core-tests:
+ name: Core Tests
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Setup Node
+ uses: actions/setup-node@v4
+ with:
+ node-version: 22
+ cache: npm
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Run core tests
+ run: npm run test:core
+
+ cypress-tests:
+ name: Cypress Tests
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Setup Node
+ uses: actions/setup-node@v4
+ with:
+ node-version: 22
+ cache: npm
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Run Cypress suite
+ uses: cypress-io/github-action@v6
+ with:
+ install: false
+ command: npm run test:cypress:all
+
+ playwright-tests:
+ name: Playwright Tests
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Setup Node
+ uses: actions/setup-node@v4
+ with:
+ node-version: 22
+ cache: npm
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Install Playwright browsers
+ run: npx playwright install --with-deps chromium
+
+ - name: Run Playwright suite
+ run: npm run test:playwright
diff --git a/.gitignore b/.gitignore
index a522e6f..9225d50 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,7 @@
-node_modules
-.idea
-cypress/screenshots
-cypress/videos
-.DS_Store
\ No newline at end of file
+node_modules
+.idea
+cypress/screenshots
+cypress/videos
+.DS_Store
+packages/playwright-ag-grid/test-results
+packages/playwright-ag-grid/playwright-report
diff --git a/README.md b/README.md
index 3ff0512..9ee49b0 100644
--- a/README.md
+++ b/README.md
@@ -1,414 +1,37 @@
-# cypress-ag-grid
-Cypress plugin for interacting with and validating against ag grid.
+# ag-grid-testing
-## Table of Contents
- * [Installation](#installation)
- * [Usage](#usage)
- + [Grid Interaction](#)
- - [Getting Data From the Grid](#getting-data-from-the-grid)
- - [Getting Select Row Data](#getting-select-row-data)
- - [Getting Elements From the Grid](#getting-elements-from-the-grid)
- - [Sorting Columns](#sorting-columns)
- - [Pinning Columns](#pinning-columns)
- + [Grid Filtering](#)
- - [Filter Options](#filter-options)
- - [Filter by Text - Column Menu](#filter-by-text---column-menu)
- - [Filterby Text - Floating Filter](#filterby-text---floating-filter)
- - [Filter by Checkbox - Column Menu](#filter-by-checkbox---column-menu)
- - [Filtering - Localization and Internationalization](#filtering---localization-and-internationalization)
- - [Add or Remove Columns](#add-or-remove-columns)
- + [Grid Validation](#)
- - [Validate Paginated Table](#validate-paginated-table)
- - [Validate Table in the Exact Order](#validate-table-in-the-exact-order)
- - [Validate Subset of Table Data](#validate-subset-of-table-data)
- - [Validate Empty Grid](#validate-empty-grid)
- * [Limitations](#limitations)
- * [Credit](#credit)
-
-
+Monorepo for Node-based AG Grid test helpers.
-## Installation
+Current packages:
-```bash
-npm install cypress-ag-grid --save-dev
-```
-Then include the following in your `support/index.js` file (Cypress v9 and below) or `support/e2e.(js|ts)` file (Cypress 10 and above):
-
-```javascript
-import "cypress-ag-grid";
-```
-## Usage
-Consider the ag grid example below:
-
-
-With the following DOM structure:
-
-
-
-### Getting Data From the Grid:
-To get the Ag Grid data, you must chain `.getAgGridData()` after the `cy.get()` command for the topmost level of the grid, including controls and headers (see selected DOM element in above image).
-
-Correct Usage:
-```javascript
-cy.get("#myGrid").getAgGridData()
-```
-
-Incorrect Usage:
-```javascript
-cy.getAgGridData();
-```
-
-The correct command will return the following:
-```json
-[
- { "Year": "2020", "Make": "Toyota", "Model": "Celica" },
- { "Year": "2020", "Make": "Ford", "Model": "Mondeo" },
- { "Year": "2020", "Make": "Porsche", "Model": "Boxter" },
- { "Year": "2020", "Make": "BMW", "Model": "3-series" },
- { "Year": "2020", "Make": "Mercedes", "Model": "GLC300" },
-]
-```
-
-
-
-### Getting Select Row Data
-To only get certain rows of data, pass the header values into the `getAgGridData()` command, like so:
-
-```javascript
-cy.get("#myGrid").getAgGridData({ onlyColumns: ["Year", "Make"] })
-```
-
-The above command will return the follwoing:
-```json
-[
- { "Year": "2020", "Make": "Toyota"},
- { "Year": "2020", "Make": "Ford"},
- { "Year": "2020", "Make": "Porsche"},
- { "Year": "2020", "Make": "BMW"},
- { "Year": "2020", "Make": "Mercedes"},
-]
-```
-
-
-
-### Getting Elements From the Grid
-To get the Ag Grid data as elements (if you want to interact with the cells themselves), you must chain `.getAgGridElements()` after the `cy.get()` command for the topmost level of the grid, including controls and headers (see selected DOM element in above image).
-```javascript
- cy.get(agGridSelector)
- .getAgGridElements()
- .then((tableElements) => {
- const porscheRow = tableElements.find(
- (row) => row.Price.innerText === "72000"
- );
- const priceCell = porscheRow.Price;
- cy.wrap(priceCell).dblclick().type("66000{enter}");
- });
-
-```
-
-The above example will grab the table as elements, finds the row whose `Price` equals `72000`. It then gets the `Price` cell for that row, double clicks on it to enable an editable input, and changes the value of the cell.
-
-
-
-### Sorting Columns
-This command will sort the specified column by the sort direction specified.
-
-Defintion:
-`.agGridSortColumn(columnName:String, sortDirection:String)`
-
-Example:
-
-```javascript
-cy.get("#myGrid").agGridSortColumn("Model", "descending");
-```
-
-
-
-### Pinning Columns
-This command will pin the specified column.
-Definition
-`.agGridPinColumn(columnName: string, pin: ['left', 'right', null])`
-
-Example:
-```javascript
-cy.get("#myGrid").agGridPinColumn("Model", "left") // Pins the "Model" column to the left
-cy.get("#myGrid").agGridPinColumn("Model", "right") // Pins the "Model" column to the right
-cy.get("#mGrid").agGridPinColumn("Model") // Removes the pin
-
-```
-
-
-
-### Filter Options
-
-The below filtering commands takes an `options` parameter comprised of the following properties:
-
-```javascript
-options: {
- searchCriteria: [{
- columnName: string;
- filterValue: string;
- operator?: string;
- isMultiFilter?: boolean;
- }];
- hasApplyButton?: boolean;
- noMenuTabs?: boolean;
- selectAllLocaleText: string;
-}
-
-/**
-- options.searchCriteria JSON with search properties and options
-- options.searchCriteria.columnName name of the column to filter
-- options.searchCriteria.filterValue value to input into the filter textbox
-- options.searchCriteria.searchInputIndex [Optional] Uses 0 by default. Index of which filter box to use in event of having multiple search conditionals
-- options.searchCriteria.operator [Optional] Use if using a search operator (i.e. Less Than, Equals, etc...use filterOperator.enum values).
-- options.searchCriteria.isMultiFilter [Optional] Used when floating filter menu has checkbox options vs freeform text input.
-- options.hasApplyButton [Optional] True if "Apply" button is used, false if filters by text input automatically.
-- options.noMenuTabs [Optional] True if you use, for example, the community edition of ag-grid, which has no menu tabs
-- options.selectAllLocaleText [Optional] Pass in the locale text value of "Select All" for when you are filtering by checkbox - this will first deselect the "Select All" option before selecting your filter value
-*/
-```
-
-### Filter by Text - Column Menu
-This command will filter a column by a text value from its menu. In the options, you must specify a `searchCriteria` objects containing one or more objects with `columnName`, `filterValue`, and optionally `operator` (i.e. Contains, Not contains, Equals, etc.).
-
-
-
-Definition: `.agGridColumnFilterTextMenu(options: {})`
+- `@kpmck/ag-grid-core`: framework-agnostic DOM and utility logic
+- `cypress-ag-grid`: Cypress plugin package, preserving the existing npm package name
+- `playwright-ag-grid`: Playwright adapter package
-Example:
-```javascript
-cy.get("#myGrid").agGridColumnFilterTextMenu({
- searchCriteria:[{
- columnName: "Model",
- filterValue: "GLC300",
- operator:"Equals"
- },
- {
- columnName: "Make",
- filterValue: "Mercedes",
- operator:"Equals"
- }
- ],
- hasApplyButton: false
-})
-````
-The above command will filter the Model column for the value 'GLC300' and set the filter operator to 'Equals'. It will then apply a secondary filter on the Make column for 'Mercedes'.
-
-
-### Filterby Text - Floating Filter
-This command will filter a column by a text value from its floating filter (if applicable). This command will filter a column by a text value from its floating menu. In the options, you must specify a `searchCriteria` object with `columnName`, `filterValue`, and optionally `operator` (i.e. Contains, Not contains, Equals, etc.) and `searchInputIndex` in the event you wish to apply multiple text conditions (see below for multi-condition example).
+## Workspace Scripts
-
+From the repository root:
-Definition: .agGridColumnFilterTextFloating(options: {})
-
-Example:
-```
- cy.get(agGridSelector).agGridColumnFilterTextFloating({
- searchCriteria: {
- columnName: "Make",
- filterValue: "Ford",
- },
- hasApplyButton: true,
- });
-```
-
-The above example will search for the Make `Ford` from the floating text menu filter.
-
-If you have the option for multiple conditions on the floating filter, you can do two searches, specifying the `searchInputIndex` parameter in the `searchCriteria` object. The below example will ssarch for any `Make` that contains `B` AND `MW`:
-
-Example:
-```
- cy.get(agGridSelector).agGridColumnFilterTextFloating({
- searchCriteria: {
- columnName: "Make",
- filterValue: "B",
- searchInputIndex: 0,
- },
- hasApplyButton: true,
- });
- cy.get(agGridSelector).agGridColumnFilterTextFloating({
- searchCriteria: {
- columnName: "Make",
- filterValue: "MW",
- searchInputIndex: 1,
- },
- hasApplyButton: true,
- });
-```
-
-
-For `Between`, pass the lower and upper bounds as two entries for the same column. The command will target the first and second visible inputs for that single `Between` condition:
-
-```javascript
- cy.get(agGridSelector).agGridColumnFilterTextFloating({
- searchCriteria: [
- {
- columnName: "Year",
- filterValue: "1990",
- operator: filterOperator.inRange,
- },
- {
- columnName: "Year",
- filterValue: "2011",
- operator: filterOperator.inRange,
- },
- ],
- hasApplyButton: true,
- });
-```
-
-
-
-
-### Filter by Checkbox - Column Menu
-This command will filter a column by a checkbox text value from its menu.
-
-
-Definition:
-```javascript
-.agGridColumnFilterCheckboxMenu(options={})
-```
-
-Example:
-```javascript
- cy.get("#myGrid").agGridColumnFilterCheckboxMenu({
- searchCriteria: {
- columnName: "Model",
- filterValue: "2002",
- },
- hasApplyButton: true,
- });
-
-```
-
-
-### Filtering - Localization and Internationalization
-When we filter by checkbox, we first deselect the Select All checkbox to ensure we ONLY select the specified checkbox. Since AG grid allows for localization, we need a way to be able to pass in the localeText for Select All. This is the only area of this plugin that has a hard-coded value, so no other localization accommodations are needed.
-
-```
- cy.get("#myGrid").agGridColumnFilterCheckboxMenu({
- searchCriteria: {
- columnName: "Model",
- filterValue: "2002",
- },
- selectAllLocaleText: "Tout Sélectionner"
- hasApplyButton: true,
- });
-```
-
-
-### Add or Remove Columns
-This command will toggle the specified column from the grid's sidebar.
-
-Definition:`.agGridToggleColumnsSideBar(columnName:String, doRemove:boolean)`
-
-Example:
-```javascript
-// This will remove the column "Year" from the grid
-cy.get("#myGrid").agGridToggleColumnsSideBar("Year", true);
-```
-
-
-
-### Validate Paginated Table
-This command will validate the paginated grid's data. The supplied expectedPaginatedTableData must be paginated as it's shown in the grid.
-
-Definition: `agGridValidatePaginatedTable(expectedPaginatedTableData, onlyColumns = {})`
-
-Example:
-```javascript
- const expectedPaginatedTableData = [
- [
- { "Year": "2020", "Make": "Toyota", "Model": "Celica" },
- { "Year": "2020", "Make": "Ford", "Model": "Mondeo" },
- { "Year": "2020", "Make": "Porsche", "Model": "Boxter" },
- { "Year": "2020", "Make": "BMW", "Model": "3-series" },
- { "Year": "2020", "Make": "Mercedes", "Model": "GLC300" },
- ],
- [
- { "Year": "2020", "Make": "Honda", "Model": "Civic" },
- { "Year": "2020", "Make": "Honda", "Model": "Accord" },
- { "Year": "2020", "Make": "Ford", "Model": "Taurus" },
- { "Year": "2020", "Make": "Hyundai", "Model": "Elantra" },
- { "Year": "2020", "Make": "Toyota", "Model": "Celica" },
- ],
- ...other table data
- ];
- cy.get("#myGrid").agGridValidatePaginatedTable(
- expectedPaginatedTableData, onlyColumns ={"Year", "Make", "Model"}
- );
- });
-```
-
-
-
-### Validate Table in the Exact Order
-This command will verify the table data is displayed exactly in the same order as the supplied expected table data. This will ONLY validate the first page of a paginated table.
-
-Definition: `.agGridValidateRowsExactOrder(actualTableData, expectedTableData)`
-
-Example:
-```javascript
-cy.get("#myGrid")
-.getAgGridData()
-.then((actualTableData) => {
- cy.agGridValidateRowsExactOrder(actualTableData, expectedTableData);
-});
+```bash
+npm run test
+npm run test:v33
+npm run test:v34
+npm run test:v35
+npm run test:watch
```
-
-
-### Validate Subset of Table Data
-This command will validate a subset of the table data. Ideal for verifying one or more records, or verify records without specified columns.
+These forward to the `cypress-ag-grid` workspace package.
-Definition:: `agGridValidateRowsSubset(actualTableData, expectedTableData)`
+## Package Layout
-Example:
-```javascript
- const expectedTableData = [
- { "Year": "2020", "Make": "Toyota", "Model": "Celica" },
- { "Year": "2020", "Make": "Ford", "Model": "Mondeo" },
- { "Year": "2020", "Make": "Porsche", "Model": "Boxter" },
- { "Year": "2020", "Make": "BMW", "Model": "3-series" },
- { "Year": "2020", "Make": "Mercedes", "Model": "GLC300" },
- ];
- cy.get(agGridSelector)
- .getAgGridData({ onlyColumns: ["Year", "Make", "Model"] })
- .then((actualTableData) => {
- cy.agGridValidateRowsSubset(actualTableData, expectedTableData);
- });
- });
+```text
+packages/
+ ag-grid-core/
+ cypress-ag-grid/
+ playwright-ag-grid/
```
-
-
-
-### Validate Empty Grid
-This will verify the table data is empty.
-Definition:`agGridValidateEmptyTable(actualTableData, expectedTableData)`
-
-Example:
-```javascript
- cy.get(agGridSelector)
- .getAgGridData()
- .then((actualTableData) => {
- cy.agGridValidateEmptyTable(actualTableData);
- });
-```
+## Repository Naming
-## Limitations
-* ~~Unable to validate deeply nested row groups~~ As of v2.x, using `.getAgGridElements()` you should be able to accomplish this.
-* ~~Unable to validate deeply nested column groups~~ As of v2.x, using `.getAgGridElements()` you should be able to accomplish this.
-* Unable to validate the entirety of an unlimited scrolling grid.
-* Unable to validate data that is out of view. The DOM will register the ag grid data as it's scrolled into view.
- * To combat this, in your code where the ag grid is called, check if the Cypress window is controlling the app and set the ag grid object to `.sizeColumnsToFit()`. You can see an example of this in the `app/grid.js` file of this repository. Read more [here](https://www.ag-grid.com/javascript-grid/column-sizing/#size-columns-to-fit)
- * Example:
- ```javascript
- if(window.Cypress){
- this.api.sizeColumnsToFit();
- }
- ```
-## Credit
-A portion of the logic to retrieve table data was expanded upon from the project [Cypress-Get-Table](https://github.com/roggerfe/cypress-get-table) by [Rogger Fernandez](https://github.com/roggerfe).
+The local monorepo root now uses the neutral workspace name `ag-grid-testing`.
+If you rename the GitHub repository to match, update the repository URLs in the package manifests at the same time.
diff --git a/RELEASING.md b/RELEASING.md
new file mode 100644
index 0000000..ddeee49
--- /dev/null
+++ b/RELEASING.md
@@ -0,0 +1,49 @@
+# Releasing
+
+This repository uses npm workspaces plus Changesets for versioning and publishing.
+
+## What Publishes
+
+- `cypress-ag-grid`
+- `playwright-ag-grid`
+- `@kpmck/ag-grid-core`
+
+Each package publishes to npm using the `name` field in its own `package.json`.
+
+## Creating A Release Entry
+
+After changing one or more publishable packages:
+
+```bash
+npm run changeset
+```
+
+Choose the packages that changed and whether each change is a patch, minor, or major release. Commit the generated markdown file in `.changeset/`.
+
+## Local Commands
+
+```bash
+npm run version-packages
+npm run release
+```
+
+- `version-packages` applies pending changesets, updates package versions, and updates internal dependency ranges.
+- `release` publishes the changed packages to npm.
+
+## GitHub Actions
+
+- `.github/workflows/ci.yml` runs the core, Cypress, and Playwright test suites on pull requests and pushes to `main`.
+
+Automated publishing is currently disabled. Releases are intended to be versioned and published manually from a local machine for now.
+
+## Manual Release Flow
+
+1. Run `npm run changeset` and commit the generated changeset file.
+2. When you are ready to cut a release, run:
+
+```bash
+npm run version-packages
+npm run release
+```
+
+3. Commit the version updates and tags produced by the release process as needed.
diff --git a/package-lock.json b/package-lock.json
index 9addce3..ad88ea1 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,15 +1,442 @@
{
- "name": "cypress-ag-grid",
+ "name": "ag-grid-testing",
"version": "3.3.5",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
- "name": "cypress-ag-grid",
- "version": "3.3.5",
- "license": "MIT",
+ "name": "ag-grid-testing",
+ "workspaces": [
+ "packages/*"
+ ],
"devDependencies": {
- "cypress": "^15.12.0"
+ "@changesets/cli": "^2.30.0"
+ }
+ },
+ "node_modules/@babel/runtime": {
+ "version": "7.29.2",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz",
+ "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@changesets/apply-release-plan": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/@changesets/apply-release-plan/-/apply-release-plan-7.1.0.tgz",
+ "integrity": "sha512-yq8ML3YS7koKQ/9bk1PqO0HMzApIFNwjlwCnwFEXMzNe8NpzeeYYKCmnhWJGkN8g7E51MnWaSbqRcTcdIxUgnQ==",
+ "dev": true,
+ "dependencies": {
+ "@changesets/config": "^3.1.3",
+ "@changesets/get-version-range-type": "^0.4.0",
+ "@changesets/git": "^3.0.4",
+ "@changesets/should-skip-package": "^0.1.2",
+ "@changesets/types": "^6.1.0",
+ "@manypkg/get-packages": "^1.1.3",
+ "detect-indent": "^6.0.0",
+ "fs-extra": "^7.0.1",
+ "lodash.startcase": "^4.4.0",
+ "outdent": "^0.5.0",
+ "prettier": "^2.7.1",
+ "resolve-from": "^5.0.0",
+ "semver": "^7.5.3"
+ }
+ },
+ "node_modules/@changesets/apply-release-plan/node_modules/fs-extra": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz",
+ "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
+ "dev": true,
+ "dependencies": {
+ "graceful-fs": "^4.1.2",
+ "jsonfile": "^4.0.0",
+ "universalify": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=6 <7 || >=8"
+ }
+ },
+ "node_modules/@changesets/apply-release-plan/node_modules/jsonfile": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
+ "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
+ "dev": true,
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/@changesets/apply-release-plan/node_modules/universalify": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
+ "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 4.0.0"
+ }
+ },
+ "node_modules/@changesets/assemble-release-plan": {
+ "version": "6.0.9",
+ "resolved": "https://registry.npmjs.org/@changesets/assemble-release-plan/-/assemble-release-plan-6.0.9.tgz",
+ "integrity": "sha512-tPgeeqCHIwNo8sypKlS3gOPmsS3wP0zHt67JDuL20P4QcXiw/O4Hl7oXiuLnP9yg+rXLQ2sScdV1Kkzde61iSQ==",
+ "dev": true,
+ "dependencies": {
+ "@changesets/errors": "^0.2.0",
+ "@changesets/get-dependents-graph": "^2.1.3",
+ "@changesets/should-skip-package": "^0.1.2",
+ "@changesets/types": "^6.1.0",
+ "@manypkg/get-packages": "^1.1.3",
+ "semver": "^7.5.3"
+ }
+ },
+ "node_modules/@changesets/changelog-git": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/@changesets/changelog-git/-/changelog-git-0.2.1.tgz",
+ "integrity": "sha512-x/xEleCFLH28c3bQeQIyeZf8lFXyDFVn1SgcBiR2Tw/r4IAWlk1fzxCEZ6NxQAjF2Nwtczoen3OA2qR+UawQ8Q==",
+ "dev": true,
+ "dependencies": {
+ "@changesets/types": "^6.1.0"
+ }
+ },
+ "node_modules/@changesets/cli": {
+ "version": "2.30.0",
+ "resolved": "https://registry.npmjs.org/@changesets/cli/-/cli-2.30.0.tgz",
+ "integrity": "sha512-5D3Nk2JPqMI1wK25pEymeWRSlSMdo5QOGlyfrKg0AOufrUcjEE3RQgaCpHoBiM31CSNrtSgdJ0U6zL1rLDDfBA==",
+ "dev": true,
+ "dependencies": {
+ "@changesets/apply-release-plan": "^7.1.0",
+ "@changesets/assemble-release-plan": "^6.0.9",
+ "@changesets/changelog-git": "^0.2.1",
+ "@changesets/config": "^3.1.3",
+ "@changesets/errors": "^0.2.0",
+ "@changesets/get-dependents-graph": "^2.1.3",
+ "@changesets/get-release-plan": "^4.0.15",
+ "@changesets/git": "^3.0.4",
+ "@changesets/logger": "^0.1.1",
+ "@changesets/pre": "^2.0.2",
+ "@changesets/read": "^0.6.7",
+ "@changesets/should-skip-package": "^0.1.2",
+ "@changesets/types": "^6.1.0",
+ "@changesets/write": "^0.4.0",
+ "@inquirer/external-editor": "^1.0.2",
+ "@manypkg/get-packages": "^1.1.3",
+ "ansi-colors": "^4.1.3",
+ "enquirer": "^2.4.1",
+ "fs-extra": "^7.0.1",
+ "mri": "^1.2.0",
+ "package-manager-detector": "^0.2.0",
+ "picocolors": "^1.1.0",
+ "resolve-from": "^5.0.0",
+ "semver": "^7.5.3",
+ "spawndamnit": "^3.0.1",
+ "term-size": "^2.1.0"
+ },
+ "bin": {
+ "changeset": "bin.js"
+ }
+ },
+ "node_modules/@changesets/cli/node_modules/fs-extra": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz",
+ "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
+ "dev": true,
+ "dependencies": {
+ "graceful-fs": "^4.1.2",
+ "jsonfile": "^4.0.0",
+ "universalify": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=6 <7 || >=8"
+ }
+ },
+ "node_modules/@changesets/cli/node_modules/jsonfile": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
+ "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
+ "dev": true,
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/@changesets/cli/node_modules/universalify": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
+ "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 4.0.0"
+ }
+ },
+ "node_modules/@changesets/config": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/@changesets/config/-/config-3.1.3.tgz",
+ "integrity": "sha512-vnXjcey8YgBn2L1OPWd3ORs0bGC4LoYcK/ubpgvzNVr53JXV5GiTVj7fWdMRsoKUH7hhhMAQnsJUqLr21EncNw==",
+ "dev": true,
+ "dependencies": {
+ "@changesets/errors": "^0.2.0",
+ "@changesets/get-dependents-graph": "^2.1.3",
+ "@changesets/logger": "^0.1.1",
+ "@changesets/should-skip-package": "^0.1.2",
+ "@changesets/types": "^6.1.0",
+ "@manypkg/get-packages": "^1.1.3",
+ "fs-extra": "^7.0.1",
+ "micromatch": "^4.0.8"
+ }
+ },
+ "node_modules/@changesets/config/node_modules/fs-extra": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz",
+ "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
+ "dev": true,
+ "dependencies": {
+ "graceful-fs": "^4.1.2",
+ "jsonfile": "^4.0.0",
+ "universalify": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=6 <7 || >=8"
+ }
+ },
+ "node_modules/@changesets/config/node_modules/jsonfile": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
+ "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
+ "dev": true,
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/@changesets/config/node_modules/universalify": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
+ "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 4.0.0"
+ }
+ },
+ "node_modules/@changesets/errors": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/@changesets/errors/-/errors-0.2.0.tgz",
+ "integrity": "sha512-6BLOQUscTpZeGljvyQXlWOItQyU71kCdGz7Pi8H8zdw6BI0g3m43iL4xKUVPWtG+qrrL9DTjpdn8eYuCQSRpow==",
+ "dev": true,
+ "dependencies": {
+ "extendable-error": "^0.1.5"
+ }
+ },
+ "node_modules/@changesets/get-dependents-graph": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/@changesets/get-dependents-graph/-/get-dependents-graph-2.1.3.tgz",
+ "integrity": "sha512-gphr+v0mv2I3Oxt19VdWRRUxq3sseyUpX9DaHpTUmLj92Y10AGy+XOtV+kbM6L/fDcpx7/ISDFK6T8A/P3lOdQ==",
+ "dev": true,
+ "dependencies": {
+ "@changesets/types": "^6.1.0",
+ "@manypkg/get-packages": "^1.1.3",
+ "picocolors": "^1.1.0",
+ "semver": "^7.5.3"
+ }
+ },
+ "node_modules/@changesets/get-release-plan": {
+ "version": "4.0.15",
+ "resolved": "https://registry.npmjs.org/@changesets/get-release-plan/-/get-release-plan-4.0.15.tgz",
+ "integrity": "sha512-Q04ZaRPuEVZtA+auOYgFaVQQSA98dXiVe/yFaZfY7hoSmQICHGvP0TF4u3EDNHWmmCS4ekA/XSpKlSM2PyTS2g==",
+ "dev": true,
+ "dependencies": {
+ "@changesets/assemble-release-plan": "^6.0.9",
+ "@changesets/config": "^3.1.3",
+ "@changesets/pre": "^2.0.2",
+ "@changesets/read": "^0.6.7",
+ "@changesets/types": "^6.1.0",
+ "@manypkg/get-packages": "^1.1.3"
+ }
+ },
+ "node_modules/@changesets/get-version-range-type": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/@changesets/get-version-range-type/-/get-version-range-type-0.4.0.tgz",
+ "integrity": "sha512-hwawtob9DryoGTpixy1D3ZXbGgJu1Rhr+ySH2PvTLHvkZuQ7sRT4oQwMh0hbqZH1weAooedEjRsbrWcGLCeyVQ==",
+ "dev": true
+ },
+ "node_modules/@changesets/git": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@changesets/git/-/git-3.0.4.tgz",
+ "integrity": "sha512-BXANzRFkX+XcC1q/d27NKvlJ1yf7PSAgi8JG6dt8EfbHFHi4neau7mufcSca5zRhwOL8j9s6EqsxmT+s+/E6Sw==",
+ "dev": true,
+ "dependencies": {
+ "@changesets/errors": "^0.2.0",
+ "@manypkg/get-packages": "^1.1.3",
+ "is-subdir": "^1.1.1",
+ "micromatch": "^4.0.8",
+ "spawndamnit": "^3.0.1"
+ }
+ },
+ "node_modules/@changesets/logger": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/@changesets/logger/-/logger-0.1.1.tgz",
+ "integrity": "sha512-OQtR36ZlnuTxKqoW4Sv6x5YIhOmClRd5pWsjZsddYxpWs517R0HkyiefQPIytCVh4ZcC5x9XaG8KTdd5iRQUfg==",
+ "dev": true,
+ "dependencies": {
+ "picocolors": "^1.1.0"
+ }
+ },
+ "node_modules/@changesets/parse": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/@changesets/parse/-/parse-0.4.3.tgz",
+ "integrity": "sha512-ZDmNc53+dXdWEv7fqIUSgRQOLYoUom5Z40gmLgmATmYR9NbL6FJJHwakcCpzaeCy+1D0m0n7mT4jj2B/MQPl7A==",
+ "dev": true,
+ "dependencies": {
+ "@changesets/types": "^6.1.0",
+ "js-yaml": "^4.1.1"
+ }
+ },
+ "node_modules/@changesets/pre": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/@changesets/pre/-/pre-2.0.2.tgz",
+ "integrity": "sha512-HaL/gEyFVvkf9KFg6484wR9s0qjAXlZ8qWPDkTyKF6+zqjBe/I2mygg3MbpZ++hdi0ToqNUF8cjj7fBy0dg8Ug==",
+ "dev": true,
+ "dependencies": {
+ "@changesets/errors": "^0.2.0",
+ "@changesets/types": "^6.1.0",
+ "@manypkg/get-packages": "^1.1.3",
+ "fs-extra": "^7.0.1"
+ }
+ },
+ "node_modules/@changesets/pre/node_modules/fs-extra": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz",
+ "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
+ "dev": true,
+ "dependencies": {
+ "graceful-fs": "^4.1.2",
+ "jsonfile": "^4.0.0",
+ "universalify": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=6 <7 || >=8"
+ }
+ },
+ "node_modules/@changesets/pre/node_modules/jsonfile": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
+ "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
+ "dev": true,
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/@changesets/pre/node_modules/universalify": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
+ "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 4.0.0"
+ }
+ },
+ "node_modules/@changesets/read": {
+ "version": "0.6.7",
+ "resolved": "https://registry.npmjs.org/@changesets/read/-/read-0.6.7.tgz",
+ "integrity": "sha512-D1G4AUYGrBEk8vj8MGwf75k9GpN6XL3wg8i42P2jZZwFLXnlr2Pn7r9yuQNbaMCarP7ZQWNJbV6XLeysAIMhTA==",
+ "dev": true,
+ "dependencies": {
+ "@changesets/git": "^3.0.4",
+ "@changesets/logger": "^0.1.1",
+ "@changesets/parse": "^0.4.3",
+ "@changesets/types": "^6.1.0",
+ "fs-extra": "^7.0.1",
+ "p-filter": "^2.1.0",
+ "picocolors": "^1.1.0"
+ }
+ },
+ "node_modules/@changesets/read/node_modules/fs-extra": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz",
+ "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
+ "dev": true,
+ "dependencies": {
+ "graceful-fs": "^4.1.2",
+ "jsonfile": "^4.0.0",
+ "universalify": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=6 <7 || >=8"
+ }
+ },
+ "node_modules/@changesets/read/node_modules/jsonfile": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
+ "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
+ "dev": true,
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/@changesets/read/node_modules/universalify": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
+ "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 4.0.0"
+ }
+ },
+ "node_modules/@changesets/should-skip-package": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/@changesets/should-skip-package/-/should-skip-package-0.1.2.tgz",
+ "integrity": "sha512-qAK/WrqWLNCP22UDdBTMPH5f41elVDlsNyat180A33dWxuUDyNpg6fPi/FyTZwRriVjg0L8gnjJn2F9XAoF0qw==",
+ "dev": true,
+ "dependencies": {
+ "@changesets/types": "^6.1.0",
+ "@manypkg/get-packages": "^1.1.3"
+ }
+ },
+ "node_modules/@changesets/types": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/@changesets/types/-/types-6.1.0.tgz",
+ "integrity": "sha512-rKQcJ+o1nKNgeoYRHKOS07tAMNd3YSN0uHaJOZYjBAgxfV7TUE7JE+z4BzZdQwb5hKaYbayKN5KrYV7ODb2rAA==",
+ "dev": true
+ },
+ "node_modules/@changesets/write": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/@changesets/write/-/write-0.4.0.tgz",
+ "integrity": "sha512-CdTLvIOPiCNuH71pyDu3rA+Q0n65cmAbXnwWH84rKGiFumFzkmHNT8KHTMEchcxN+Kl8I54xGUhJ7l3E7X396Q==",
+ "dev": true,
+ "dependencies": {
+ "@changesets/types": "^6.1.0",
+ "fs-extra": "^7.0.1",
+ "human-id": "^4.1.1",
+ "prettier": "^2.7.1"
+ }
+ },
+ "node_modules/@changesets/write/node_modules/fs-extra": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz",
+ "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
+ "dev": true,
+ "dependencies": {
+ "graceful-fs": "^4.1.2",
+ "jsonfile": "^4.0.0",
+ "universalify": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=6 <7 || >=8"
+ }
+ },
+ "node_modules/@changesets/write/node_modules/jsonfile": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
+ "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
+ "dev": true,
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/@changesets/write/node_modules/universalify": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
+ "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 4.0.0"
}
},
"node_modules/@cypress/request": {
@@ -60,6 +487,183 @@
"ms": "^2.1.1"
}
},
+ "node_modules/@inquirer/external-editor": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz",
+ "integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==",
+ "dev": true,
+ "dependencies": {
+ "chardet": "^2.1.1",
+ "iconv-lite": "^0.7.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@types/node": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@kpmck/ag-grid-core": {
+ "resolved": "packages/ag-grid-core",
+ "link": true
+ },
+ "node_modules/@manypkg/find-root": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@manypkg/find-root/-/find-root-1.1.0.tgz",
+ "integrity": "sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/runtime": "^7.5.5",
+ "@types/node": "^12.7.1",
+ "find-up": "^4.1.0",
+ "fs-extra": "^8.1.0"
+ }
+ },
+ "node_modules/@manypkg/find-root/node_modules/@types/node": {
+ "version": "12.20.55",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz",
+ "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==",
+ "dev": true
+ },
+ "node_modules/@manypkg/find-root/node_modules/fs-extra": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
+ "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
+ "dev": true,
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^4.0.0",
+ "universalify": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=6 <7 || >=8"
+ }
+ },
+ "node_modules/@manypkg/find-root/node_modules/jsonfile": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
+ "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
+ "dev": true,
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/@manypkg/find-root/node_modules/universalify": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
+ "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 4.0.0"
+ }
+ },
+ "node_modules/@manypkg/get-packages": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@manypkg/get-packages/-/get-packages-1.1.3.tgz",
+ "integrity": "sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==",
+ "dev": true,
+ "dependencies": {
+ "@babel/runtime": "^7.5.5",
+ "@changesets/types": "^4.0.1",
+ "@manypkg/find-root": "^1.1.0",
+ "fs-extra": "^8.1.0",
+ "globby": "^11.0.0",
+ "read-yaml-file": "^1.1.0"
+ }
+ },
+ "node_modules/@manypkg/get-packages/node_modules/@changesets/types": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/@changesets/types/-/types-4.1.0.tgz",
+ "integrity": "sha512-LDQvVDv5Kb50ny2s25Fhm3d9QSZimsoUGBsUioj6MC3qbMUCuC8GPIvk/M6IvXx3lYhAs0lwWUQLb+VIEUCECw==",
+ "dev": true
+ },
+ "node_modules/@manypkg/get-packages/node_modules/fs-extra": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
+ "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
+ "dev": true,
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^4.0.0",
+ "universalify": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=6 <7 || >=8"
+ }
+ },
+ "node_modules/@manypkg/get-packages/node_modules/jsonfile": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
+ "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
+ "dev": true,
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/@manypkg/get-packages/node_modules/universalify": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
+ "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 4.0.0"
+ }
+ },
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true,
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@playwright/test": {
+ "version": "1.59.1",
+ "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.59.1.tgz",
+ "integrity": "sha512-PG6q63nQg5c9rIi4/Z5lR5IVF7yU5MqmKaPOe0HSc0O2cX1fPi96sUQu5j7eo4gKCkB2AnNGoWt7y4/Xx3Kcqg==",
+ "dev": true,
+ "dependencies": {
+ "playwright": "1.59.1"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/@types/node": {
"version": "18.18.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.3.tgz",
@@ -176,6 +780,21 @@
}
]
},
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true
+ },
+ "node_modules/array-union": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
+ "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/asn1": {
"version": "0.2.6",
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
@@ -262,6 +881,18 @@
"tweetnacl": "^0.14.3"
}
},
+ "node_modules/better-path-resolve": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/better-path-resolve/-/better-path-resolve-1.0.0.tgz",
+ "integrity": "sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==",
+ "dev": true,
+ "dependencies": {
+ "is-windows": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/blob-util": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/blob-util/-/blob-util-2.0.2.tgz",
@@ -274,6 +905,18 @@
"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==",
"dev": true
},
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dev": true,
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/buffer": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
@@ -379,6 +1022,12 @@
"node": ">=8"
}
},
+ "node_modules/chardet": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.1.tgz",
+ "integrity": "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==",
+ "dev": true
+ },
"node_modules/ci-info": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz",
@@ -588,6 +1237,10 @@
"node": "^20.1.0 || ^22.0.0 || >=24.0.0"
}
},
+ "node_modules/cypress-ag-grid": {
+ "resolved": "packages/cypress-ag-grid",
+ "link": true
+ },
"node_modules/cypress/node_modules/tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
@@ -644,6 +1297,27 @@
"node": ">=0.4.0"
}
},
+ "node_modules/detect-indent": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz",
+ "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/dir-glob": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
+ "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
+ "dev": true,
+ "dependencies": {
+ "path-type": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
@@ -684,12 +1358,13 @@
}
},
"node_modules/enquirer": {
- "version": "2.3.6",
- "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz",
- "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==",
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz",
+ "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==",
"dev": true,
"dependencies": {
- "ansi-colors": "^4.1.1"
+ "ansi-colors": "^4.1.1",
+ "strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8.6"
@@ -749,6 +1424,19 @@
"node": ">=0.8.0"
}
},
+ "node_modules/esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+ "dev": true,
+ "bin": {
+ "esparse": "bin/esparse.js",
+ "esvalidate": "bin/esvalidate.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/eventemitter2": {
"version": "6.4.7",
"resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.7.tgz",
@@ -796,6 +1484,12 @@
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
"dev": true
},
+ "node_modules/extendable-error": {
+ "version": "0.1.7",
+ "resolved": "https://registry.npmjs.org/extendable-error/-/extendable-error-0.1.7.tgz",
+ "integrity": "sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==",
+ "dev": true
+ },
"node_modules/extract-zip": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
@@ -825,6 +1519,31 @@
"node >=0.6.0"
]
},
+ "node_modules/fast-glob": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
+ "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
+ "dev": true,
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.8"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
+ "node_modules/fastq": {
+ "version": "1.20.1",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
+ "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==",
+ "dev": true,
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
"node_modules/fd-slicer": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
@@ -849,6 +1568,31 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "dev": true,
+ "dependencies": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/forever-agent": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
@@ -889,6 +1633,20 @@
"node": ">=10"
}
},
+ "node_modules/fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "dev": true,
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@@ -959,6 +1717,18 @@
"assert-plus": "^1.0.0"
}
},
+ "node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/global-dirs": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.0.tgz",
@@ -974,6 +1744,26 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/globby": {
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
+ "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
+ "dev": true,
+ "dependencies": {
+ "array-union": "^2.1.0",
+ "dir-glob": "^3.0.1",
+ "fast-glob": "^3.2.9",
+ "ignore": "^5.2.0",
+ "merge2": "^1.4.1",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
@@ -1079,6 +1869,15 @@
"node": ">=0.10"
}
},
+ "node_modules/human-id": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/human-id/-/human-id-4.1.3.tgz",
+ "integrity": "sha512-tsYlhAYpjCKa//8rXZ9DqKEawhPoSytweBC2eNvcaDK+57RZLHGqNs3PZTQO6yekLFSuvA6AlnAfrw1uBvtb+Q==",
+ "dev": true,
+ "bin": {
+ "human-id": "dist/cli.js"
+ }
+ },
"node_modules/human-signals": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz",
@@ -1088,6 +1887,22 @@
"node": ">=8.12.0"
}
},
+ "node_modules/iconv-lite": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
+ "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
+ "dev": true,
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
@@ -1108,6 +1923,15 @@
}
]
},
+ "node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true,
+ "engines": {
+ "node": ">= 4"
+ }
+ },
"node_modules/indent-string": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
@@ -1126,6 +1950,15 @@
"node": ">=10"
}
},
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
@@ -1135,6 +1968,18 @@
"node": ">=8"
}
},
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/is-installed-globally": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz",
@@ -1151,6 +1996,15 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
"node_modules/is-path-inside": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
@@ -1172,6 +2026,18 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/is-subdir": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/is-subdir/-/is-subdir-1.2.0.tgz",
+ "integrity": "sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw==",
+ "dev": true,
+ "dependencies": {
+ "better-path-resolve": "1.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/is-typedarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
@@ -1190,6 +2056,15 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/is-windows": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz",
+ "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@@ -1202,6 +2077,18 @@
"integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==",
"dev": true
},
+ "node_modules/js-yaml": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
+ "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
+ "dev": true,
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
"node_modules/jsbn": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
@@ -1274,6 +2161,18 @@
}
}
},
+ "node_modules/locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "dev": true,
+ "dependencies": {
+ "p-locate": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/lodash": {
"version": "4.17.23",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
@@ -1286,6 +2185,12 @@
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
"dev": true
},
+ "node_modules/lodash.startcase": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/lodash.startcase/-/lodash.startcase-4.4.0.tgz",
+ "integrity": "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==",
+ "dev": true
+ },
"node_modules/log-symbols": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
@@ -1366,6 +2271,28 @@
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
"dev": true
},
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "dev": true,
+ "dependencies": {
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
@@ -1405,6 +2332,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/mri": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
+ "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -1465,6 +2401,60 @@
"integrity": "sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA==",
"dev": true
},
+ "node_modules/outdent": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/outdent/-/outdent-0.5.0.tgz",
+ "integrity": "sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==",
+ "dev": true
+ },
+ "node_modules/p-filter": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/p-filter/-/p-filter-2.1.0.tgz",
+ "integrity": "sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==",
+ "dev": true,
+ "dependencies": {
+ "p-map": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/p-filter/node_modules/p-map": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz",
+ "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "dev": true,
+ "dependencies": {
+ "p-try": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "dev": true,
+ "dependencies": {
+ "p-limit": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/p-map": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz",
@@ -1480,6 +2470,33 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/p-try": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/package-manager-detector": {
+ "version": "0.2.11",
+ "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-0.2.11.tgz",
+ "integrity": "sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==",
+ "dev": true,
+ "dependencies": {
+ "quansync": "^0.2.7"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
@@ -1489,6 +2506,15 @@
"node": ">=8"
}
},
+ "node_modules/path-type": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
+ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/pend": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
@@ -1501,6 +2527,24 @@
"integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==",
"dev": true
},
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
+ "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
"node_modules/pify": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
@@ -1510,6 +2554,55 @@
"node": ">=0.10.0"
}
},
+ "node_modules/playwright": {
+ "version": "1.59.1",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.59.1.tgz",
+ "integrity": "sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw==",
+ "dev": true,
+ "dependencies": {
+ "playwright-core": "1.59.1"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "fsevents": "2.3.2"
+ }
+ },
+ "node_modules/playwright-ag-grid": {
+ "resolved": "packages/playwright-ag-grid",
+ "link": true
+ },
+ "node_modules/playwright-core": {
+ "version": "1.59.1",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.59.1.tgz",
+ "integrity": "sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==",
+ "dev": true,
+ "bin": {
+ "playwright-core": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/prettier": {
+ "version": "2.8.8",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz",
+ "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==",
+ "dev": true,
+ "bin": {
+ "prettier": "bin-prettier.js"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
"node_modules/pretty-bytes": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz",
@@ -1562,6 +2655,88 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/quansync": {
+ "version": "0.2.11",
+ "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz",
+ "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/antfu"
+ },
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/sxzz"
+ }
+ ]
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/read-yaml-file": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/read-yaml-file/-/read-yaml-file-1.1.0.tgz",
+ "integrity": "sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==",
+ "dev": true,
+ "dependencies": {
+ "graceful-fs": "^4.1.5",
+ "js-yaml": "^3.6.1",
+ "pify": "^4.0.1",
+ "strip-bom": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/read-yaml-file/node_modules/argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "dev": true,
+ "dependencies": {
+ "sprintf-js": "~1.0.2"
+ }
+ },
+ "node_modules/read-yaml-file/node_modules/js-yaml": {
+ "version": "3.14.2",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
+ "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
+ "dev": true,
+ "dependencies": {
+ "argparse": "^1.0.7",
+ "esprima": "^4.0.0"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/read-yaml-file/node_modules/pify": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
+ "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/request-progress": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz",
@@ -1571,6 +2746,15 @@
"throttleit": "^1.0.0"
}
},
+ "node_modules/resolve-from": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
+ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/restore-cursor": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
@@ -1584,12 +2768,45 @@
"node": ">=8"
}
},
+ "node_modules/reusify": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
+ "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
+ "dev": true,
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/rfdc": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz",
"integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==",
"dev": true
},
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
"node_modules/rxjs": {
"version": "7.5.5",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.5.tgz",
@@ -1625,6 +2842,18 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"dev": true
},
+ "node_modules/semver": {
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "dev": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -1724,6 +2953,15 @@
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
"dev": true
},
+ "node_modules/slash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
+ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/slice-ansi": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz",
@@ -1738,6 +2976,34 @@
"node": ">=8"
}
},
+ "node_modules/spawndamnit": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/spawndamnit/-/spawndamnit-3.0.1.tgz",
+ "integrity": "sha512-MmnduQUuHCoFckZoWnXsTg7JaiLBJrKFj9UI2MbRPGaJeVpsLcVBu6P/IGZovziM/YBsellCmsprgNA+w0CzVg==",
+ "dev": true,
+ "dependencies": {
+ "cross-spawn": "^7.0.5",
+ "signal-exit": "^4.0.1"
+ }
+ },
+ "node_modules/spawndamnit/node_modules/signal-exit": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "dev": true,
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
+ "dev": true
+ },
"node_modules/sshpk": {
"version": "1.18.0",
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz",
@@ -1789,6 +3055,15 @@
"node": ">=8"
}
},
+ "node_modules/strip-bom": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+ "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/strip-final-newline": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
@@ -1839,6 +3114,18 @@
"url": "https://www.buymeacoffee.com/systeminfo"
}
},
+ "node_modules/term-size": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz",
+ "integrity": "sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/throttleit": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz",
@@ -1878,6 +3165,18 @@
"node": ">=14.14"
}
},
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
"node_modules/tough-cookie": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz",
@@ -2023,6 +3322,31 @@
"buffer-crc32": "~0.2.3",
"fd-slicer": "~1.1.0"
}
+ },
+ "packages/ag-grid-core": {
+ "name": "@kpmck/ag-grid-core",
+ "version": "0.1.0",
+ "license": "MIT"
+ },
+ "packages/cypress-ag-grid": {
+ "version": "3.3.5",
+ "license": "MIT",
+ "dependencies": {
+ "@kpmck/ag-grid-core": "0.1.0"
+ },
+ "devDependencies": {
+ "cypress": "^15.12.0"
+ }
+ },
+ "packages/playwright-ag-grid": {
+ "version": "0.1.0",
+ "license": "MIT",
+ "dependencies": {
+ "@kpmck/ag-grid-core": "0.1.0"
+ },
+ "devDependencies": {
+ "@playwright/test": "^1.52.0"
+ }
}
}
}
diff --git a/package.json b/package.json
index 5a4a211..0728a9c 100644
--- a/package.json
+++ b/package.json
@@ -1,32 +1,30 @@
{
- "name": "cypress-ag-grid",
- "version": "3.3.5",
- "description": "Cypress plugin to interact with ag grid",
- "main": "src/index.js",
- "repository": {
- "type": "git",
- "url": "git+https://github.com/kpmck/cypress-ag-grid.git"
- },
- "keywords": [
- "aggrid",
- "ag-grid",
- "cypress",
- "cypress-io",
- "e2e testing",
- "cypress table",
- "cypress ag grid",
- "cypress aggrid"
+ "name": "ag-grid-testing",
+ "private": true,
+ "workspaces": [
+ "packages/*"
],
"scripts": {
- "test": "npx cypress run --headless --spec \"cypress/e2e/*.cy.js\"",
- "test:v33": "npx cypress run --headless --spec \"cypress/e2e/*.v33.cy.js\"",
- "test:v34": "npx cypress run --headless --spec \"cypress/e2e/*.v34.cy.js\"",
- "test:v35": "npx cypress run --headless --spec \"cypress/e2e/*.v35.cy.js\"",
- "test:watch": "npx cypress open"
+ "changeset": "changeset",
+ "version-packages": "changeset version",
+ "release": "changeset publish",
+ "test:core": "npm run test --workspace @kpmck/ag-grid-core",
+ "test:cypress": "npm run test:all --workspace cypress-ag-grid",
+ "test:cypress:all": "npm run test:all --workspace cypress-ag-grid",
+ "test:cypress:v33": "npm run test:v33 --workspace cypress-ag-grid",
+ "test:cypress:v34": "npm run test:v34 --workspace cypress-ag-grid",
+ "test:cypress:v35": "npm run test:v35 --workspace cypress-ag-grid",
+ "test:cypress:watch": "npm run test:watch --workspace cypress-ag-grid",
+ "test:playwright": "npm run test:all --workspace playwright-ag-grid",
+ "test:playwright:all": "npm run test:all --workspace playwright-ag-grid",
+ "test:playwright:v33": "npm run test:v33 --workspace playwright-ag-grid",
+ "test:playwright:v34": "npm run test:v34 --workspace playwright-ag-grid",
+ "test:playwright:v35": "npm run test:v35 --workspace playwright-ag-grid",
+ "test:playwright:watch": "npm run test:watch --workspace playwright-ag-grid",
+ "test:all": "npm run test:core && npm run test:cypress:all && npm run test:playwright:all",
+ "test": "npm run test:all"
},
- "author": "Kerry McKeever ",
- "license": "MIT",
"devDependencies": {
- "cypress": "^15.12.0"
+ "@changesets/cli": "^2.30.0"
}
}
diff --git a/packages/ag-grid-core/README.md b/packages/ag-grid-core/README.md
new file mode 100644
index 0000000..c690371
--- /dev/null
+++ b/packages/ag-grid-core/README.md
@@ -0,0 +1,11 @@
+# @kpmck/ag-grid-core
+
+Framework-agnostic AG Grid helpers shared by the adapter packages in this monorepo.
+
+Planned responsibilities:
+
+- DOM traversal and extraction
+- AG Grid animation waiting helpers
+- Shared types and selectors
+
+Framework-specific command registration and assertions stay in adapter packages such as `cypress-ag-grid` and `playwright-ag-grid`.
diff --git a/packages/ag-grid-core/package.json b/packages/ag-grid-core/package.json
new file mode 100644
index 0000000..4e1f967
--- /dev/null
+++ b/packages/ag-grid-core/package.json
@@ -0,0 +1,31 @@
+{
+ "name": "@kpmck/ag-grid-core",
+ "version": "0.1.0",
+ "description": "Framework-agnostic AG Grid DOM helpers for Node-based test tooling",
+ "type": "module",
+ "main": "src/index.js",
+ "types": "src/index.d.ts",
+ "exports": {
+ ".": "./src/index.js"
+ },
+ "publishConfig": {
+ "access": "public"
+ },
+ "scripts": {
+ "test": "node --test"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/kpmck/cypress-ag-grid.git"
+ },
+ "keywords": [
+ "ag-grid",
+ "aggrid",
+ "testing",
+ "dom",
+ "playwright",
+ "cypress"
+ ],
+ "author": "Kerry McKeever ",
+ "license": "MIT"
+}
diff --git a/packages/ag-grid-core/src/index.d.ts b/packages/ag-grid-core/src/index.d.ts
new file mode 100644
index 0000000..f70ac15
--- /dev/null
+++ b/packages/ag-grid-core/src/index.d.ts
@@ -0,0 +1,44 @@
+export interface AgGridExtractionOptions {
+ onlyColumns?: string[];
+ valuesArray?: boolean;
+ returnElements?: boolean;
+ timeoutMs?: number;
+}
+
+export declare const filterOperator: Record;
+export declare const sort: Record;
+export declare const filterTab: Record;
+
+export declare function isRowNotDestroyed(rowElement: Element): boolean;
+
+export declare function waitForAgGridAnimation(
+ agGridRootElement: Element,
+ options?: AgGridExtractionOptions
+): Promise;
+
+export declare function getAgGridHeaders(tableElement: Element): string[];
+
+export declare function extractAgGrid(
+ agGridRootElement: Element,
+ options?: AgGridExtractionOptions
+): Array> | { headers: string[]; rows: Array> };
+
+export declare function extractAgGridData(
+ agGridRootElement: Element,
+ options?: AgGridExtractionOptions
+): Promise> | { headers: string[]; rows: string[][] }>;
+
+export declare function extractAgGridElements(
+ agGridRootElement: Element,
+ options?: AgGridExtractionOptions
+): Promise> | { headers: string[]; rows: Element[][] }>;
+
+export declare function browserExtractAgGrid(
+ agGridRootElement: Element,
+ options?: AgGridExtractionOptions
+): Array> | { headers: string[]; rows: string[][] };
+
+export declare function browserWaitForAgGridAnimation(
+ agGridRootElement: Element,
+ options?: AgGridExtractionOptions
+): Promise;
diff --git a/packages/ag-grid-core/src/index.js b/packages/ag-grid-core/src/index.js
new file mode 100644
index 0000000..cd91ffa
--- /dev/null
+++ b/packages/ag-grid-core/src/index.js
@@ -0,0 +1,452 @@
+const AG_GRID_COLUMN_SELECTORS = [
+ ".ag-pinned-left-cols-container",
+ ".ag-center-cols-clipper",
+ ".ag-center-cols-viewport",
+ ".ag-pinned-right-cols-container",
+];
+
+const DEFAULT_ANIMATION_TIMEOUT_MS = 5000;
+
+export const filterOperator = {
+ contains: "Contains",
+ notContains: "Does not contain",
+ equals: "Equals",
+ notEquals: "Does not equal",
+ startsWith: "Begins with",
+ endsWith: "Ends with",
+ lessThan: "Less than",
+ lessThanOrEquals: "Less than or equal to",
+ greaterThan: "Greater than",
+ greaterThanOrEquals: "Greater than or equal to",
+ inRange: "Between",
+ blank: "Blank",
+ notBlank: "Not blank",
+};
+
+export const sort = {
+ ascending: "asc",
+ descending: "desc",
+ none: "none",
+};
+
+export const filterTab = {
+ columns: "columns",
+ filter: "filter",
+ search: "search",
+ general: "menu",
+};
+
+function isElementNode(value) {
+ return Boolean(value && value.nodeType === 1);
+}
+
+function getAttributeValue(element, attribute) {
+ const attributeNode = element?.attributes?.[attribute];
+
+ if (!attributeNode) {
+ return undefined;
+ }
+
+ if (typeof attributeNode.value === "string") {
+ return attributeNode.value;
+ }
+
+ if (typeof attributeNode.nodeValue === "string") {
+ return attributeNode.nodeValue;
+ }
+
+ return undefined;
+}
+
+function sortElementsByAttributeValue(attribute) {
+ return (a, b) => {
+ const contentA = parseInt(getAttributeValue(a, attribute), 10).valueOf();
+ const contentB = parseInt(getAttributeValue(b, attribute), 10).valueOf();
+ return contentA < contentB ? -1 : contentA > contentB ? 1 : 0;
+ };
+}
+
+function getTrimmedTextContent(element) {
+ return element?.textContent?.trim?.() ?? "";
+}
+
+export function isRowNotDestroyed(rowElement) {
+ const rect = rowElement.getBoundingClientRect();
+ const viewPortRect = rowElement.parentElement.getBoundingClientRect();
+
+ return (
+ rect.top >= viewPortRect.top &&
+ rect.left >= viewPortRect.left &&
+ rect.bottom <= viewPortRect.bottom &&
+ rect.right <= viewPortRect.right
+ );
+}
+
+export async function waitForAgGridAnimation(
+ agGridRootElement,
+ options = {}
+) {
+ if (!isElementNode(agGridRootElement)) {
+ throw new Error(`Couldn't find a valid AG Grid root element.`);
+ }
+
+ const timeoutMs = options.timeoutMs ?? DEFAULT_ANIMATION_TIMEOUT_MS;
+ const animations = agGridRootElement.getAnimations?.({ subtree: true }) ?? [];
+
+ const agGridAnimations = animations.filter((animation) => {
+ const animationTarget = animation.effect?.target;
+
+ if (
+ !isElementNode(animationTarget) ||
+ !animationTarget.classList
+ ) {
+ return false;
+ }
+
+ const hasAgGridClass = [...animationTarget.classList].some((className) =>
+ className.startsWith("ag-")
+ );
+
+ return animationTarget === agGridRootElement || hasAgGridClass;
+ });
+
+ const finiteAnimations = agGridAnimations.filter((animation) => {
+ const iterations = animation.effect?.getTiming?.()?.iterations;
+ return iterations !== Infinity;
+ });
+
+ await Promise.race([
+ Promise.all(
+ finiteAnimations.map(async (animation) => {
+ try {
+ await animation.finished;
+ } catch (error) {
+ if (error?.name === "AbortError") {
+ return;
+ }
+
+ throw error;
+ }
+ })
+ ),
+ new Promise((resolve) => {
+ setTimeout(resolve, timeoutMs);
+ }),
+ ]);
+}
+
+export function getAgGridHeaders(tableElement) {
+ return [
+ ...tableElement.querySelectorAll(".ag-header-row-column [aria-colindex]"),
+ ]
+ .sort(sortElementsByAttributeValue("aria-colindex"))
+ .map((headerElement) => {
+ const headerCells = [
+ ...headerElement.querySelectorAll(".ag-header-cell-text"),
+ ];
+
+ if (headerCells.length === 0) {
+ return [getTrimmedTextContent(headerElement)];
+ }
+
+ return headerCells.map((element) => getTrimmedTextContent(element));
+ })
+ .flat();
+}
+
+function getRowCells(rowElement) {
+ const rowCells = [...rowElement.querySelectorAll(".ag-cell[aria-colindex]")];
+
+ if (rowCells.length > 0) {
+ return rowCells;
+ }
+
+ return [...rowElement.querySelectorAll(".ag-cell")];
+}
+
+function getStructuredRows(allRows, returnElements) {
+ if (!allRows.length) {
+ return [];
+ }
+
+ return allRows
+ .filter((rowCells) => rowCells.length)
+ .map((rowCells) =>
+ rowCells
+ .sort(sortElementsByAttributeValue("aria-colindex"))
+ .map((element) =>
+ returnElements ? element : getTrimmedTextContent(element)
+ )
+ );
+}
+
+function mapRowsToObjects(headers, rows, options = {}) {
+ return rows.map((row) =>
+ row.reduce((acc, curr, idx) => {
+ if (
+ (options.onlyColumns && !options.onlyColumns.includes(headers[idx])) ||
+ headers[idx] === undefined
+ ) {
+ return acc;
+ }
+
+ return { ...acc, [headers[idx]]: curr };
+ }, {})
+ );
+}
+
+export function extractAgGrid(tableRootElement, options = {}) {
+ if (!isElementNode(tableRootElement)) {
+ throw new Error(`Couldn't find a valid AG Grid element.`);
+ }
+
+ const returnElements = options.returnElements ?? false;
+ const tableElement = tableRootElement.querySelectorAll(".ag-root")[0];
+
+ if (!tableElement) {
+ throw new Error("The provided element does not contain an .ag-root node.");
+ }
+
+ const headers = getAgGridHeaders(tableElement);
+ let allRows = [];
+
+ AG_GRID_COLUMN_SELECTORS.forEach((selector) => {
+ [
+ ...tableElement.querySelectorAll(
+ `${selector}:not(.ag-hidden) .ag-row:not(.ag-opacity-zero)`
+ ),
+ ]
+ .filter(isRowNotDestroyed)
+ .sort(sortElementsByAttributeValue("row-index"))
+ .forEach((rowElement) => {
+ const rowCells = getRowCells(rowElement);
+ const rowIndex = parseInt(getAttributeValue(rowElement, "row-index"), 10);
+
+ if (allRows[rowIndex]) {
+ allRows[rowIndex] = [...allRows[rowIndex], ...rowCells];
+ } else {
+ allRows[rowIndex] = rowCells;
+ }
+ });
+ });
+
+ allRows = allRows
+ .filter((row) => row.length)
+ .map((row) => row.filter((cell, index) => row.indexOf(cell) === index));
+
+ const rows = getStructuredRows(allRows, returnElements);
+
+ if (options.valuesArray) {
+ return { headers, rows };
+ }
+
+ return mapRowsToObjects(headers, rows, options);
+}
+
+export async function extractAgGridData(agGridRootElement, options = {}) {
+ await waitForAgGridAnimation(agGridRootElement, options);
+ return extractAgGrid(agGridRootElement, options);
+}
+
+export async function extractAgGridElements(agGridRootElement, options = {}) {
+ await waitForAgGridAnimation(agGridRootElement, options);
+ return extractAgGrid(agGridRootElement, { ...options, returnElements: true });
+}
+
+// Self-contained exports that can be serialized into a browser context by Playwright.
+export async function browserWaitForAgGridAnimation(
+ agGridRootElement,
+ options = {}
+) {
+ function isElementNode(value) {
+ return Boolean(value && value.nodeType === 1);
+ }
+
+ if (!isElementNode(agGridRootElement)) {
+ throw new Error(`Couldn't find a valid AG Grid root element.`);
+ }
+
+ const timeoutMs = options.timeoutMs ?? 5000;
+ const animations = agGridRootElement.getAnimations?.({ subtree: true }) ?? [];
+
+ const agGridAnimations = animations.filter((animation) => {
+ const animationTarget = animation.effect?.target;
+
+ if (!isElementNode(animationTarget) || !animationTarget.classList) {
+ return false;
+ }
+
+ const hasAgGridClass = [...animationTarget.classList].some((className) =>
+ className.startsWith("ag-")
+ );
+
+ return animationTarget === agGridRootElement || hasAgGridClass;
+ });
+
+ const finiteAnimations = agGridAnimations.filter((animation) => {
+ const iterations = animation.effect?.getTiming?.()?.iterations;
+ return iterations !== Infinity;
+ });
+
+ await Promise.race([
+ Promise.all(
+ finiteAnimations.map(async (animation) => {
+ try {
+ await animation.finished;
+ } catch (error) {
+ if (error?.name === "AbortError") {
+ return;
+ }
+
+ throw error;
+ }
+ })
+ ),
+ new Promise((resolve) => setTimeout(resolve, timeoutMs)),
+ ]);
+}
+
+export function browserExtractAgGrid(agGridRootElement, options = {}) {
+ const agGridColumnSelectors = [
+ ".ag-pinned-left-cols-container",
+ ".ag-center-cols-clipper",
+ ".ag-center-cols-viewport",
+ ".ag-pinned-right-cols-container",
+ ];
+
+ function isElementNode(value) {
+ return Boolean(value && value.nodeType === 1);
+ }
+
+ function getAttributeValue(element, attribute) {
+ const attributeNode = element?.attributes?.[attribute];
+
+ if (!attributeNode) {
+ return undefined;
+ }
+
+ if (typeof attributeNode.value === "string") {
+ return attributeNode.value;
+ }
+
+ if (typeof attributeNode.nodeValue === "string") {
+ return attributeNode.nodeValue;
+ }
+
+ return undefined;
+ }
+
+ function sortElementsByAttributeValue(attribute) {
+ return (a, b) => {
+ const contentA = parseInt(getAttributeValue(a, attribute), 10).valueOf();
+ const contentB = parseInt(getAttributeValue(b, attribute), 10).valueOf();
+ return contentA < contentB ? -1 : contentA > contentB ? 1 : 0;
+ };
+ }
+
+ function getTrimmedTextContent(element) {
+ return element?.textContent?.trim?.() ?? "";
+ }
+
+ function isRowNotDestroyedLocal(rowElement) {
+ const rect = rowElement.getBoundingClientRect();
+ const viewPortRect = rowElement.parentElement.getBoundingClientRect();
+
+ return (
+ rect.top >= viewPortRect.top &&
+ rect.left >= viewPortRect.left &&
+ rect.bottom <= viewPortRect.bottom &&
+ rect.right <= viewPortRect.right
+ );
+ }
+
+ function getRowCells(rowElement) {
+ const rowCells = [...rowElement.querySelectorAll(".ag-cell[aria-colindex]")];
+ if (rowCells.length > 0) {
+ return rowCells;
+ }
+
+ return [...rowElement.querySelectorAll(".ag-cell")];
+ }
+
+ if (!isElementNode(agGridRootElement)) {
+ throw new Error(`Couldn't find a valid AG Grid element.`);
+ }
+
+ const returnElements = options.returnElements ?? false;
+ const tableElement = agGridRootElement.querySelectorAll(".ag-root")[0];
+
+ if (!tableElement) {
+ throw new Error("The provided element does not contain an .ag-root node.");
+ }
+
+ const headers = [
+ ...tableElement.querySelectorAll(".ag-header-row-column [aria-colindex]"),
+ ]
+ .sort(sortElementsByAttributeValue("aria-colindex"))
+ .map((headerElement) => {
+ const headerCells = [
+ ...headerElement.querySelectorAll(".ag-header-cell-text"),
+ ];
+
+ if (headerCells.length === 0) {
+ return [getTrimmedTextContent(headerElement)];
+ }
+
+ return headerCells.map((element) => getTrimmedTextContent(element));
+ })
+ .flat();
+
+ let allRows = [];
+
+ agGridColumnSelectors.forEach((selector) => {
+ [
+ ...tableElement.querySelectorAll(
+ `${selector}:not(.ag-hidden) .ag-row:not(.ag-opacity-zero)`
+ ),
+ ]
+ .filter(isRowNotDestroyedLocal)
+ .sort(sortElementsByAttributeValue("row-index"))
+ .forEach((rowElement) => {
+ const rowCells = getRowCells(rowElement);
+ const rowIndex = parseInt(getAttributeValue(rowElement, "row-index"), 10);
+
+ if (allRows[rowIndex]) {
+ allRows[rowIndex] = [...allRows[rowIndex], ...rowCells];
+ } else {
+ allRows[rowIndex] = rowCells;
+ }
+ });
+ });
+
+ allRows = allRows
+ .filter((row) => row.length)
+ .map((row) => row.filter((cell, index) => row.indexOf(cell) === index));
+
+ const rows = allRows
+ .filter((rowCells) => rowCells.length)
+ .map((rowCells) =>
+ rowCells
+ .sort(sortElementsByAttributeValue("aria-colindex"))
+ .map((element) =>
+ returnElements ? getAttributeValue(element, "col-id") ?? getTrimmedTextContent(element) : getTrimmedTextContent(element)
+ )
+ );
+
+ if (options.valuesArray) {
+ return { headers, rows };
+ }
+
+ return rows.map((row) =>
+ row.reduce((acc, curr, idx) => {
+ if (
+ (options.onlyColumns && !options.onlyColumns.includes(headers[idx])) ||
+ headers[idx] === undefined
+ ) {
+ return acc;
+ }
+
+ return { ...acc, [headers[idx]]: curr };
+ }, {})
+ );
+}
diff --git a/packages/ag-grid-core/src/index.test.js b/packages/ag-grid-core/src/index.test.js
new file mode 100644
index 0000000..ed266d2
--- /dev/null
+++ b/packages/ag-grid-core/src/index.test.js
@@ -0,0 +1,203 @@
+import test from "node:test";
+import assert from "node:assert/strict";
+
+import {
+ extractAgGridData,
+ extractAgGridElements,
+ waitForAgGridAnimation,
+} from "./index.js";
+
+function createAttributes(attributes = {}) {
+ return Object.fromEntries(
+ Object.entries(attributes).map(([key, value]) => [
+ key,
+ { nodeValue: String(value), value: String(value) },
+ ])
+ );
+}
+
+class FakeElement {
+ constructor({
+ textContent = "",
+ attributes = {},
+ selectorMap = {},
+ rect = { top: 0, left: 0, bottom: 10, right: 10 },
+ classList = [],
+ nodeType = 1,
+ } = {}) {
+ this.textContent = textContent;
+ this.attributes = createAttributes(attributes);
+ this.selectorMap = selectorMap;
+ this._rect = rect;
+ this.classList = classList;
+ this.nodeType = nodeType;
+ this.parentElement = null;
+ }
+
+ querySelectorAll(selector) {
+ return this.selectorMap[selector] ?? [];
+ }
+
+ getBoundingClientRect() {
+ return this._rect;
+ }
+
+ setSelectorMap(selectorMap) {
+ this.selectorMap = selectorMap;
+ return this;
+ }
+}
+
+function connect(parent, children) {
+ children.forEach((child) => {
+ child.parentElement = parent;
+ });
+}
+
+function createCell(colIndex, text) {
+ return new FakeElement({
+ textContent: text,
+ attributes: { "aria-colindex": colIndex },
+ });
+}
+
+function createRow(rowIndex, cells, rect = { top: 0, left: 0, bottom: 10, right: 10 }) {
+ const row = new FakeElement({
+ attributes: { "row-index": rowIndex },
+ rect,
+ });
+
+ connect(row, cells);
+ row.setSelectorMap({
+ ".ag-cell[aria-colindex]": cells,
+ ".ag-cell": cells,
+ });
+
+ const viewport = new FakeElement({
+ rect: { top: 0, left: 0, bottom: 100, right: 100 },
+ });
+ row.parentElement = viewport;
+
+ return row;
+}
+
+function createHeader(colIndex, text) {
+ const textElement = new FakeElement({ textContent: text });
+ const header = new FakeElement({
+ attributes: { "aria-colindex": colIndex },
+ });
+
+ connect(header, [textElement]);
+ header.setSelectorMap({
+ ".ag-header-cell-text": [textElement],
+ });
+
+ return header;
+}
+
+function createGridFixture() {
+ const headerYear = createHeader(1, "Year");
+ const headerMake = createHeader(2, "Make");
+ const headerModel = createHeader(3, "Model");
+
+ const pinnedLeftRow0 = createRow(0, [createCell(1, "2020")]);
+ const centerRow0 = createRow(0, [createCell(2, "Toyota"), createCell(3, "Celica")]);
+ const centerRow1 = createRow(1, [createCell(1, "2021"), createCell(2, "Ford"), createCell(3, "Mondeo")]);
+ const destroyedRow = createRow(
+ 2,
+ [createCell(1, "9999"), createCell(2, "Ghost"), createCell(3, "Row")],
+ { top: 200, left: 0, bottom: 210, right: 10 }
+ );
+
+ const agRoot = new FakeElement();
+ agRoot.setSelectorMap({
+ ".ag-header-row-column [aria-colindex]": [headerModel, headerYear, headerMake],
+ ".ag-pinned-left-cols-container:not(.ag-hidden) .ag-row:not(.ag-opacity-zero)": [pinnedLeftRow0],
+ ".ag-center-cols-clipper:not(.ag-hidden) .ag-row:not(.ag-opacity-zero)": [centerRow0, centerRow1, destroyedRow],
+ ".ag-center-cols-viewport:not(.ag-hidden) .ag-row:not(.ag-opacity-zero)": [],
+ ".ag-pinned-right-cols-container:not(.ag-hidden) .ag-row:not(.ag-opacity-zero)": [],
+ });
+
+ const gridRoot = new FakeElement();
+ gridRoot.setSelectorMap({
+ ".ag-root": [agRoot],
+ });
+
+ return { gridRoot, pinnedLeftRow0, centerRow0 };
+}
+
+test("extractAgGridData returns structured row data and filters destroyed rows", async () => {
+ const { gridRoot } = createGridFixture();
+ gridRoot.getAnimations = () => [];
+
+ const rows = await extractAgGridData(gridRoot);
+
+ assert.deepEqual(rows, [
+ { Year: "2020", Make: "Toyota", Model: "Celica" },
+ { Year: "2021", Make: "Ford", Model: "Mondeo" },
+ ]);
+});
+
+test("extractAgGridData supports onlyColumns and valuesArray", async () => {
+ const { gridRoot } = createGridFixture();
+ gridRoot.getAnimations = () => [];
+
+ const subset = await extractAgGridData(gridRoot, { onlyColumns: ["Make"] });
+ const arrays = await extractAgGridData(gridRoot, { valuesArray: true });
+
+ assert.deepEqual(subset, [{ Make: "Toyota" }, { Make: "Ford" }]);
+ assert.deepEqual(arrays.headers, ["Year", "Make", "Model"]);
+ assert.deepEqual(arrays.rows, [
+ ["2020", "Toyota", "Celica"],
+ ["2021", "Ford", "Mondeo"],
+ ]);
+});
+
+test("extractAgGridElements returns cell elements instead of text", async () => {
+ const { gridRoot, pinnedLeftRow0, centerRow0 } = createGridFixture();
+ gridRoot.getAnimations = () => [];
+
+ const rows = await extractAgGridElements(gridRoot);
+
+ assert.equal(rows[0].Year, pinnedLeftRow0.querySelectorAll(".ag-cell")[0]);
+ assert.equal(rows[0].Make, centerRow0.querySelectorAll(".ag-cell")[0]);
+});
+
+test("waitForAgGridAnimation waits for AG Grid animations and ignores aborts", async () => {
+ let resolved = false;
+
+ const target = new FakeElement({ classList: ["ag-root"] });
+ const root = new FakeElement();
+ root.getAnimations = () => [
+ {
+ effect: {
+ target,
+ getTiming: () => ({ iterations: 1 }),
+ },
+ finished: new Promise((resolve) => {
+ setTimeout(() => {
+ resolved = true;
+ resolve();
+ }, 10);
+ }),
+ },
+ {
+ effect: {
+ target,
+ getTiming: () => ({ iterations: 1 }),
+ },
+ finished: Promise.reject(Object.assign(new Error("aborted"), { name: "AbortError" })),
+ },
+ {
+ effect: {
+ target: new FakeElement({ classList: ["spinner"] }),
+ getTiming: () => ({ iterations: 1 }),
+ },
+ finished: Promise.resolve(),
+ },
+ ];
+
+ await waitForAgGridAnimation(root, { timeoutMs: 50 });
+
+ assert.equal(resolved, true);
+});
diff --git a/packages/cypress-ag-grid/README.md b/packages/cypress-ag-grid/README.md
new file mode 100644
index 0000000..3ff0512
--- /dev/null
+++ b/packages/cypress-ag-grid/README.md
@@ -0,0 +1,414 @@
+# cypress-ag-grid
+Cypress plugin for interacting with and validating against ag grid.
+
+## Table of Contents
+ * [Installation](#installation)
+ * [Usage](#usage)
+ + [Grid Interaction](#)
+ - [Getting Data From the Grid](#getting-data-from-the-grid)
+ - [Getting Select Row Data](#getting-select-row-data)
+ - [Getting Elements From the Grid](#getting-elements-from-the-grid)
+ - [Sorting Columns](#sorting-columns)
+ - [Pinning Columns](#pinning-columns)
+ + [Grid Filtering](#)
+ - [Filter Options](#filter-options)
+ - [Filter by Text - Column Menu](#filter-by-text---column-menu)
+ - [Filterby Text - Floating Filter](#filterby-text---floating-filter)
+ - [Filter by Checkbox - Column Menu](#filter-by-checkbox---column-menu)
+ - [Filtering - Localization and Internationalization](#filtering---localization-and-internationalization)
+ - [Add or Remove Columns](#add-or-remove-columns)
+ + [Grid Validation](#)
+ - [Validate Paginated Table](#validate-paginated-table)
+ - [Validate Table in the Exact Order](#validate-table-in-the-exact-order)
+ - [Validate Subset of Table Data](#validate-subset-of-table-data)
+ - [Validate Empty Grid](#validate-empty-grid)
+ * [Limitations](#limitations)
+ * [Credit](#credit)
+
+
+
+## Installation
+
+```bash
+npm install cypress-ag-grid --save-dev
+```
+Then include the following in your `support/index.js` file (Cypress v9 and below) or `support/e2e.(js|ts)` file (Cypress 10 and above):
+
+```javascript
+import "cypress-ag-grid";
+```
+## Usage
+Consider the ag grid example below:
+
+
+With the following DOM structure:
+
+
+
+### Getting Data From the Grid:
+To get the Ag Grid data, you must chain `.getAgGridData()` after the `cy.get()` command for the topmost level of the grid, including controls and headers (see selected DOM element in above image).
+
+Correct Usage:
+```javascript
+cy.get("#myGrid").getAgGridData()
+```
+
+Incorrect Usage:
+```javascript
+cy.getAgGridData();
+```
+
+The correct command will return the following:
+```json
+[
+ { "Year": "2020", "Make": "Toyota", "Model": "Celica" },
+ { "Year": "2020", "Make": "Ford", "Model": "Mondeo" },
+ { "Year": "2020", "Make": "Porsche", "Model": "Boxter" },
+ { "Year": "2020", "Make": "BMW", "Model": "3-series" },
+ { "Year": "2020", "Make": "Mercedes", "Model": "GLC300" },
+]
+```
+
+
+
+### Getting Select Row Data
+To only get certain rows of data, pass the header values into the `getAgGridData()` command, like so:
+
+```javascript
+cy.get("#myGrid").getAgGridData({ onlyColumns: ["Year", "Make"] })
+```
+
+The above command will return the follwoing:
+```json
+[
+ { "Year": "2020", "Make": "Toyota"},
+ { "Year": "2020", "Make": "Ford"},
+ { "Year": "2020", "Make": "Porsche"},
+ { "Year": "2020", "Make": "BMW"},
+ { "Year": "2020", "Make": "Mercedes"},
+]
+```
+
+
+
+### Getting Elements From the Grid
+To get the Ag Grid data as elements (if you want to interact with the cells themselves), you must chain `.getAgGridElements()` after the `cy.get()` command for the topmost level of the grid, including controls and headers (see selected DOM element in above image).
+```javascript
+ cy.get(agGridSelector)
+ .getAgGridElements()
+ .then((tableElements) => {
+ const porscheRow = tableElements.find(
+ (row) => row.Price.innerText === "72000"
+ );
+ const priceCell = porscheRow.Price;
+ cy.wrap(priceCell).dblclick().type("66000{enter}");
+ });
+
+```
+
+The above example will grab the table as elements, finds the row whose `Price` equals `72000`. It then gets the `Price` cell for that row, double clicks on it to enable an editable input, and changes the value of the cell.
+
+
+
+### Sorting Columns
+This command will sort the specified column by the sort direction specified.
+
+Defintion:
+`.agGridSortColumn(columnName:String, sortDirection:String)`
+
+Example:
+
+```javascript
+cy.get("#myGrid").agGridSortColumn("Model", "descending");
+```
+
+
+
+### Pinning Columns
+This command will pin the specified column.
+Definition
+`.agGridPinColumn(columnName: string, pin: ['left', 'right', null])`
+
+Example:
+```javascript
+cy.get("#myGrid").agGridPinColumn("Model", "left") // Pins the "Model" column to the left
+cy.get("#myGrid").agGridPinColumn("Model", "right") // Pins the "Model" column to the right
+cy.get("#mGrid").agGridPinColumn("Model") // Removes the pin
+
+```
+
+
+
+### Filter Options
+
+The below filtering commands takes an `options` parameter comprised of the following properties:
+
+```javascript
+options: {
+ searchCriteria: [{
+ columnName: string;
+ filterValue: string;
+ operator?: string;
+ isMultiFilter?: boolean;
+ }];
+ hasApplyButton?: boolean;
+ noMenuTabs?: boolean;
+ selectAllLocaleText: string;
+}
+
+/**
+- options.searchCriteria JSON with search properties and options
+- options.searchCriteria.columnName name of the column to filter
+- options.searchCriteria.filterValue value to input into the filter textbox
+- options.searchCriteria.searchInputIndex [Optional] Uses 0 by default. Index of which filter box to use in event of having multiple search conditionals
+- options.searchCriteria.operator [Optional] Use if using a search operator (i.e. Less Than, Equals, etc...use filterOperator.enum values).
+- options.searchCriteria.isMultiFilter [Optional] Used when floating filter menu has checkbox options vs freeform text input.
+- options.hasApplyButton [Optional] True if "Apply" button is used, false if filters by text input automatically.
+- options.noMenuTabs [Optional] True if you use, for example, the community edition of ag-grid, which has no menu tabs
+- options.selectAllLocaleText [Optional] Pass in the locale text value of "Select All" for when you are filtering by checkbox - this will first deselect the "Select All" option before selecting your filter value
+*/
+```
+
+### Filter by Text - Column Menu
+This command will filter a column by a text value from its menu. In the options, you must specify a `searchCriteria` objects containing one or more objects with `columnName`, `filterValue`, and optionally `operator` (i.e. Contains, Not contains, Equals, etc.).
+
+
+
+Definition: `.agGridColumnFilterTextMenu(options: {})`
+
+Example:
+```javascript
+cy.get("#myGrid").agGridColumnFilterTextMenu({
+ searchCriteria:[{
+ columnName: "Model",
+ filterValue: "GLC300",
+ operator:"Equals"
+ },
+ {
+ columnName: "Make",
+ filterValue: "Mercedes",
+ operator:"Equals"
+ }
+ ],
+ hasApplyButton: false
+})
+````
+The above command will filter the Model column for the value 'GLC300' and set the filter operator to 'Equals'. It will then apply a secondary filter on the Make column for 'Mercedes'.
+
+
+### Filterby Text - Floating Filter
+This command will filter a column by a text value from its floating filter (if applicable). This command will filter a column by a text value from its floating menu. In the options, you must specify a `searchCriteria` object with `columnName`, `filterValue`, and optionally `operator` (i.e. Contains, Not contains, Equals, etc.) and `searchInputIndex` in the event you wish to apply multiple text conditions (see below for multi-condition example).
+
+
+
+Definition: .agGridColumnFilterTextFloating(options: {})
+
+Example:
+```
+ cy.get(agGridSelector).agGridColumnFilterTextFloating({
+ searchCriteria: {
+ columnName: "Make",
+ filterValue: "Ford",
+ },
+ hasApplyButton: true,
+ });
+```
+
+The above example will search for the Make `Ford` from the floating text menu filter.
+
+If you have the option for multiple conditions on the floating filter, you can do two searches, specifying the `searchInputIndex` parameter in the `searchCriteria` object. The below example will ssarch for any `Make` that contains `B` AND `MW`:
+
+Example:
+```
+ cy.get(agGridSelector).agGridColumnFilterTextFloating({
+ searchCriteria: {
+ columnName: "Make",
+ filterValue: "B",
+ searchInputIndex: 0,
+ },
+ hasApplyButton: true,
+ });
+ cy.get(agGridSelector).agGridColumnFilterTextFloating({
+ searchCriteria: {
+ columnName: "Make",
+ filterValue: "MW",
+ searchInputIndex: 1,
+ },
+ hasApplyButton: true,
+ });
+```
+
+
+For `Between`, pass the lower and upper bounds as two entries for the same column. The command will target the first and second visible inputs for that single `Between` condition:
+
+```javascript
+ cy.get(agGridSelector).agGridColumnFilterTextFloating({
+ searchCriteria: [
+ {
+ columnName: "Year",
+ filterValue: "1990",
+ operator: filterOperator.inRange,
+ },
+ {
+ columnName: "Year",
+ filterValue: "2011",
+ operator: filterOperator.inRange,
+ },
+ ],
+ hasApplyButton: true,
+ });
+```
+
+
+
+
+### Filter by Checkbox - Column Menu
+This command will filter a column by a checkbox text value from its menu.
+
+
+Definition:
+```javascript
+.agGridColumnFilterCheckboxMenu(options={})
+```
+
+Example:
+```javascript
+ cy.get("#myGrid").agGridColumnFilterCheckboxMenu({
+ searchCriteria: {
+ columnName: "Model",
+ filterValue: "2002",
+ },
+ hasApplyButton: true,
+ });
+
+```
+
+
+### Filtering - Localization and Internationalization
+When we filter by checkbox, we first deselect the Select All checkbox to ensure we ONLY select the specified checkbox. Since AG grid allows for localization, we need a way to be able to pass in the localeText for Select All. This is the only area of this plugin that has a hard-coded value, so no other localization accommodations are needed.
+
+```
+ cy.get("#myGrid").agGridColumnFilterCheckboxMenu({
+ searchCriteria: {
+ columnName: "Model",
+ filterValue: "2002",
+ },
+ selectAllLocaleText: "Tout Sélectionner"
+ hasApplyButton: true,
+ });
+```
+
+
+### Add or Remove Columns
+This command will toggle the specified column from the grid's sidebar.
+
+Definition:`.agGridToggleColumnsSideBar(columnName:String, doRemove:boolean)`
+
+Example:
+```javascript
+// This will remove the column "Year" from the grid
+cy.get("#myGrid").agGridToggleColumnsSideBar("Year", true);
+```
+
+
+
+### Validate Paginated Table
+This command will validate the paginated grid's data. The supplied expectedPaginatedTableData must be paginated as it's shown in the grid.
+
+Definition: `agGridValidatePaginatedTable(expectedPaginatedTableData, onlyColumns = {})`
+
+Example:
+```javascript
+ const expectedPaginatedTableData = [
+ [
+ { "Year": "2020", "Make": "Toyota", "Model": "Celica" },
+ { "Year": "2020", "Make": "Ford", "Model": "Mondeo" },
+ { "Year": "2020", "Make": "Porsche", "Model": "Boxter" },
+ { "Year": "2020", "Make": "BMW", "Model": "3-series" },
+ { "Year": "2020", "Make": "Mercedes", "Model": "GLC300" },
+ ],
+ [
+ { "Year": "2020", "Make": "Honda", "Model": "Civic" },
+ { "Year": "2020", "Make": "Honda", "Model": "Accord" },
+ { "Year": "2020", "Make": "Ford", "Model": "Taurus" },
+ { "Year": "2020", "Make": "Hyundai", "Model": "Elantra" },
+ { "Year": "2020", "Make": "Toyota", "Model": "Celica" },
+ ],
+ ...other table data
+ ];
+ cy.get("#myGrid").agGridValidatePaginatedTable(
+ expectedPaginatedTableData, onlyColumns ={"Year", "Make", "Model"}
+ );
+ });
+```
+
+
+
+### Validate Table in the Exact Order
+This command will verify the table data is displayed exactly in the same order as the supplied expected table data. This will ONLY validate the first page of a paginated table.
+
+Definition: `.agGridValidateRowsExactOrder(actualTableData, expectedTableData)`
+
+Example:
+```javascript
+cy.get("#myGrid")
+.getAgGridData()
+.then((actualTableData) => {
+ cy.agGridValidateRowsExactOrder(actualTableData, expectedTableData);
+});
+```
+
+
+
+### Validate Subset of Table Data
+This command will validate a subset of the table data. Ideal for verifying one or more records, or verify records without specified columns.
+
+Definition:: `agGridValidateRowsSubset(actualTableData, expectedTableData)`
+
+Example:
+```javascript
+ const expectedTableData = [
+ { "Year": "2020", "Make": "Toyota", "Model": "Celica" },
+ { "Year": "2020", "Make": "Ford", "Model": "Mondeo" },
+ { "Year": "2020", "Make": "Porsche", "Model": "Boxter" },
+ { "Year": "2020", "Make": "BMW", "Model": "3-series" },
+ { "Year": "2020", "Make": "Mercedes", "Model": "GLC300" },
+ ];
+ cy.get(agGridSelector)
+ .getAgGridData({ onlyColumns: ["Year", "Make", "Model"] })
+ .then((actualTableData) => {
+ cy.agGridValidateRowsSubset(actualTableData, expectedTableData);
+ });
+ });
+```
+
+
+
+### Validate Empty Grid
+This will verify the table data is empty.
+
+Definition:`agGridValidateEmptyTable(actualTableData, expectedTableData)`
+
+Example:
+```javascript
+ cy.get(agGridSelector)
+ .getAgGridData()
+ .then((actualTableData) => {
+ cy.agGridValidateEmptyTable(actualTableData);
+ });
+```
+
+## Limitations
+* ~~Unable to validate deeply nested row groups~~ As of v2.x, using `.getAgGridElements()` you should be able to accomplish this.
+* ~~Unable to validate deeply nested column groups~~ As of v2.x, using `.getAgGridElements()` you should be able to accomplish this.
+* Unable to validate the entirety of an unlimited scrolling grid.
+* Unable to validate data that is out of view. The DOM will register the ag grid data as it's scrolled into view.
+ * To combat this, in your code where the ag grid is called, check if the Cypress window is controlling the app and set the ag grid object to `.sizeColumnsToFit()`. You can see an example of this in the `app/grid.js` file of this repository. Read more [here](https://www.ag-grid.com/javascript-grid/column-sizing/#size-columns-to-fit)
+ * Example:
+ ```javascript
+ if(window.Cypress){
+ this.api.sizeColumnsToFit();
+ }
+ ```
+## Credit
+A portion of the logic to retrieve table data was expanded upon from the project [Cypress-Get-Table](https://github.com/roggerfe/cypress-get-table) by [Rogger Fernandez](https://github.com/roggerfe).
diff --git a/ag-grid-example-dom.png b/packages/cypress-ag-grid/ag-grid-example-dom.png
similarity index 100%
rename from ag-grid-example-dom.png
rename to packages/cypress-ag-grid/ag-grid-example-dom.png
diff --git a/ag-grid-example-filter-checkbox-menu.png b/packages/cypress-ag-grid/ag-grid-example-filter-checkbox-menu.png
similarity index 100%
rename from ag-grid-example-filter-checkbox-menu.png
rename to packages/cypress-ag-grid/ag-grid-example-filter-checkbox-menu.png
diff --git a/ag-grid-example-filter-text-floating-multi-condition.png b/packages/cypress-ag-grid/ag-grid-example-filter-text-floating-multi-condition.png
similarity index 100%
rename from ag-grid-example-filter-text-floating-multi-condition.png
rename to packages/cypress-ag-grid/ag-grid-example-filter-text-floating-multi-condition.png
diff --git a/ag-grid-example-filter-text-floating.png b/packages/cypress-ag-grid/ag-grid-example-filter-text-floating.png
similarity index 100%
rename from ag-grid-example-filter-text-floating.png
rename to packages/cypress-ag-grid/ag-grid-example-filter-text-floating.png
diff --git a/ag-grid-example-filter-text-menu.png b/packages/cypress-ag-grid/ag-grid-example-filter-text-menu.png
similarity index 100%
rename from ag-grid-example-filter-text-menu.png
rename to packages/cypress-ag-grid/ag-grid-example-filter-text-menu.png
diff --git a/ag-grid-example.png b/packages/cypress-ag-grid/ag-grid-example.png
similarity index 100%
rename from ag-grid-example.png
rename to packages/cypress-ag-grid/ag-grid-example.png
diff --git a/app/ag-grid.css b/packages/cypress-ag-grid/app/ag-grid.css
similarity index 100%
rename from app/ag-grid.css
rename to packages/cypress-ag-grid/app/ag-grid.css
diff --git a/app/ag-theme-alpine.css b/packages/cypress-ag-grid/app/ag-theme-alpine.css
similarity index 100%
rename from app/ag-theme-alpine.css
rename to packages/cypress-ag-grid/app/ag-theme-alpine.css
diff --git a/app/animation-wait/ag-owned.html b/packages/cypress-ag-grid/app/animation-wait/ag-owned.html
similarity index 100%
rename from app/animation-wait/ag-owned.html
rename to packages/cypress-ag-grid/app/animation-wait/ag-owned.html
diff --git a/app/animation-wait/animation-grid.js b/packages/cypress-ag-grid/app/animation-wait/animation-grid.js
similarity index 100%
rename from app/animation-wait/animation-grid.js
rename to packages/cypress-ag-grid/app/animation-wait/animation-grid.js
diff --git a/app/animation-wait/third-party-subtree.html b/packages/cypress-ag-grid/app/animation-wait/third-party-subtree.html
similarity index 100%
rename from app/animation-wait/third-party-subtree.html
rename to packages/cypress-ag-grid/app/animation-wait/third-party-subtree.html
diff --git a/app/data.json b/packages/cypress-ag-grid/app/data.json
similarity index 100%
rename from app/data.json
rename to packages/cypress-ag-grid/app/data.json
diff --git a/app/grid-basic.js b/packages/cypress-ag-grid/app/grid-basic.js
similarity index 100%
rename from app/grid-basic.js
rename to packages/cypress-ag-grid/app/grid-basic.js
diff --git a/app/grid-grouped.js b/packages/cypress-ag-grid/app/grid-grouped.js
similarity index 100%
rename from app/grid-grouped.js
rename to packages/cypress-ag-grid/app/grid-grouped.js
diff --git a/app/index.html b/packages/cypress-ag-grid/app/index.html
similarity index 100%
rename from app/index.html
rename to packages/cypress-ag-grid/app/index.html
diff --git a/app/v33/index.html b/packages/cypress-ag-grid/app/v33/index.html
similarity index 100%
rename from app/v33/index.html
rename to packages/cypress-ag-grid/app/v33/index.html
diff --git a/app/v34/index.html b/packages/cypress-ag-grid/app/v34/index.html
similarity index 100%
rename from app/v34/index.html
rename to packages/cypress-ag-grid/app/v34/index.html
diff --git a/cypress.config.js b/packages/cypress-ag-grid/cypress.config.js
similarity index 100%
rename from cypress.config.js
rename to packages/cypress-ag-grid/cypress.config.js
diff --git a/cypress/e2e/ag-grid-animation-wait.v35.cy.js b/packages/cypress-ag-grid/cypress/e2e/ag-grid-animation-wait.v35.cy.js
similarity index 100%
rename from cypress/e2e/ag-grid-animation-wait.v35.cy.js
rename to packages/cypress-ag-grid/cypress/e2e/ag-grid-animation-wait.v35.cy.js
diff --git a/cypress/e2e/ag-grid-data.v33.cy.js b/packages/cypress-ag-grid/cypress/e2e/ag-grid-data.v33.cy.js
similarity index 100%
rename from cypress/e2e/ag-grid-data.v33.cy.js
rename to packages/cypress-ag-grid/cypress/e2e/ag-grid-data.v33.cy.js
diff --git a/cypress/e2e/ag-grid-data.v34.cy.js b/packages/cypress-ag-grid/cypress/e2e/ag-grid-data.v34.cy.js
similarity index 100%
rename from cypress/e2e/ag-grid-data.v34.cy.js
rename to packages/cypress-ag-grid/cypress/e2e/ag-grid-data.v34.cy.js
diff --git a/cypress/e2e/ag-grid-data.v35.cy.js b/packages/cypress-ag-grid/cypress/e2e/ag-grid-data.v35.cy.js
similarity index 100%
rename from cypress/e2e/ag-grid-data.v35.cy.js
rename to packages/cypress-ag-grid/cypress/e2e/ag-grid-data.v35.cy.js
diff --git a/cypress/e2e/ag-grid-elements.v33.cy.js b/packages/cypress-ag-grid/cypress/e2e/ag-grid-elements.v33.cy.js
similarity index 100%
rename from cypress/e2e/ag-grid-elements.v33.cy.js
rename to packages/cypress-ag-grid/cypress/e2e/ag-grid-elements.v33.cy.js
diff --git a/cypress/e2e/ag-grid-elements.v34.cy.js b/packages/cypress-ag-grid/cypress/e2e/ag-grid-elements.v34.cy.js
similarity index 100%
rename from cypress/e2e/ag-grid-elements.v34.cy.js
rename to packages/cypress-ag-grid/cypress/e2e/ag-grid-elements.v34.cy.js
diff --git a/cypress/e2e/ag-grid-elements.v35.cy.js b/packages/cypress-ag-grid/cypress/e2e/ag-grid-elements.v35.cy.js
similarity index 100%
rename from cypress/e2e/ag-grid-elements.v35.cy.js
rename to packages/cypress-ag-grid/cypress/e2e/ag-grid-elements.v35.cy.js
diff --git a/cypress/e2e/shared/run-ag-grid-data-suite.js b/packages/cypress-ag-grid/cypress/e2e/shared/run-ag-grid-data-suite.js
similarity index 100%
rename from cypress/e2e/shared/run-ag-grid-data-suite.js
rename to packages/cypress-ag-grid/cypress/e2e/shared/run-ag-grid-data-suite.js
diff --git a/cypress/e2e/shared/run-ag-grid-elements-suite.js b/packages/cypress-ag-grid/cypress/e2e/shared/run-ag-grid-elements-suite.js
similarity index 100%
rename from cypress/e2e/shared/run-ag-grid-elements-suite.js
rename to packages/cypress-ag-grid/cypress/e2e/shared/run-ag-grid-elements-suite.js
diff --git a/cypress/fixtures/cardata.json b/packages/cypress-ag-grid/cypress/fixtures/cardata.json
similarity index 100%
rename from cypress/fixtures/cardata.json
rename to packages/cypress-ag-grid/cypress/fixtures/cardata.json
diff --git a/cypress/plugins/index.js b/packages/cypress-ag-grid/cypress/plugins/index.js
similarity index 100%
rename from cypress/plugins/index.js
rename to packages/cypress-ag-grid/cypress/plugins/index.js
diff --git a/cypress/support/commands.js b/packages/cypress-ag-grid/cypress/support/commands.js
similarity index 100%
rename from cypress/support/commands.js
rename to packages/cypress-ag-grid/cypress/support/commands.js
diff --git a/cypress/support/e2e.js b/packages/cypress-ag-grid/cypress/support/e2e.js
similarity index 100%
rename from cypress/support/e2e.js
rename to packages/cypress-ag-grid/cypress/support/e2e.js
diff --git a/packages/cypress-ag-grid/package.json b/packages/cypress-ag-grid/package.json
new file mode 100644
index 0000000..f130789
--- /dev/null
+++ b/packages/cypress-ag-grid/package.json
@@ -0,0 +1,39 @@
+{
+ "name": "cypress-ag-grid",
+ "version": "3.3.5",
+ "description": "Cypress plugin to interact with ag grid",
+ "main": "src/index.js",
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/kpmck/cypress-ag-grid.git"
+ },
+ "keywords": [
+ "aggrid",
+ "ag-grid",
+ "cypress",
+ "cypress-io",
+ "e2e testing",
+ "cypress table",
+ "cypress ag grid",
+ "cypress aggrid"
+ ],
+ "scripts": {
+ "test": "npm run test:all",
+ "test:all": "npx cypress run --headless --spec \"cypress/e2e/*.cy.js\"",
+ "test:v33": "npx cypress run --headless --spec \"cypress/e2e/*.v33.cy.js\"",
+ "test:v34": "npx cypress run --headless --spec \"cypress/e2e/*.v34.cy.js\"",
+ "test:v35": "npx cypress run --headless --spec \"cypress/e2e/*.v35.cy.js\"",
+ "test:watch": "npx cypress open"
+ },
+ "publishConfig": {
+ "access": "public"
+ },
+ "author": "Kerry McKeever ",
+ "license": "MIT",
+ "dependencies": {
+ "@kpmck/ag-grid-core": "0.1.0"
+ },
+ "devDependencies": {
+ "cypress": "^15.12.0"
+ }
+}
diff --git a/src/agGrid/agGridInteractions.js b/packages/cypress-ag-grid/src/agGrid/agGridInteractions.js
similarity index 75%
rename from src/agGrid/agGridInteractions.js
rename to packages/cypress-ag-grid/src/agGrid/agGridInteractions.js
index a4e0d7f..9a31a51 100644
--- a/src/agGrid/agGridInteractions.js
+++ b/packages/cypress-ag-grid/src/agGrid/agGridInteractions.js
@@ -1,99 +1,41 @@
///
-import { filterOperator } from "./filterOperator.enum";
-import { filterTab } from "./menuTab.enum";
-import { sort } from "./sort.enum";
-
-function isRowNotDestroyed(rowElement) {
- const rect = rowElement.getBoundingClientRect();
- const viewPortRect = rowElement.parentElement.getBoundingClientRect();
-
- return (
- rect.top >= viewPortRect.top &&
- rect.left >= viewPortRect.left &&
- rect.bottom <= viewPortRect.bottom &&
- rect.right <= viewPortRect.right
- );
-}
-
-export const agGridWaitForAnimation = async (agGridElement) => {
- if (agGridElement.get().length < 1) {
+import {
+ extractAgGridData,
+ extractAgGridElements,
+ filterOperator,
+ filterTab,
+ sort,
+ waitForAgGridAnimation,
+} from "@kpmck/ag-grid-core";
+
+function getSingleAgGridRootElement(agGridElement) {
+ const rootElements = agGridElement.get();
+
+ if (rootElements.length < 1) {
throw new Error(`Couldn't find the element ${agGridElement}`);
}
- const AG_GRID_ANIMATION_TIMEOUT_MS = 5000;
- const agGridRootElement = agGridElement.get()[0];
- const animations = agGridRootElement.getAnimations({ subtree: true });
-
- const agGridAnimations = animations.filter((animation) => {
- const animationTarget = animation.effect?.target;
- if (
- !animationTarget ||
- animationTarget.nodeType !== 1 ||
- !animationTarget.classList
- ) {
- return false;
- }
-
- const hasAgGridClass = [...animationTarget.classList].some((className) =>
- className.startsWith("ag-")
- );
-
- return (
- animationTarget === agGridRootElement ||
- hasAgGridClass
+ if (rootElements.length > 1) {
+ throw new Error(
+ `Selector "${agGridElement.selector}" returned more than 1 element.`
);
- });
-
- // Filter out infinite animations (e.g. loading spinners) whose .finished
- // promise never resolves per the Web Animations API spec.
- const finiteAnimations = agGridAnimations.filter((animation) => {
- const iterations = animation.effect?.getTiming?.()?.iterations;
- return iterations !== Infinity;
- });
+ }
- await Promise.race([
- Promise.all(
- finiteAnimations.map(async (animation) => {
- try {
- await animation.finished;
- } catch (error) {
- if (error.name === "AbortError") return;
- console.error("error", error, error.name);
- throw error;
- }
- })
- ),
- new Promise((resolve) => {
- setTimeout(resolve, AG_GRID_ANIMATION_TIMEOUT_MS);
- }),
- ]);
+ return rootElements[0];
+}
+export const agGridWaitForAnimation = async (agGridElement) => {
+ await waitForAgGridAnimation(getSingleAgGridRootElement(agGridElement));
return agGridElement;
};
-/**
- * Uses the attribute value's index and sorts the data accordingly.
- * For our purposes, we are getting the attribute with the items' indices and sorting accordingly.
- *
- * @param {*} index
- * @returns
- */
-function sortElementsByAttributeValue(attribute) {
- return (a, b) => {
- const contentA = parseInt(a.attributes[attribute].nodeValue, 10).valueOf();
- const contentB = parseInt(b.attributes[attribute].nodeValue, 10).valueOf();
- return contentA < contentB ? -1 : contentA > contentB ? 1 : 0;
- };
-}
-
/**
* Retrieves the values from the *displayed* page in ag grid and assigns each value to its respective column name.
* @param agGridElement The get() selector for which ag grid table you wish to retrieve.
* @param options Provide an array of columns you wish to exclude from the table retrieval.
*/
export const getAgGridData = async (agGridElement, options = {}) => {
- await agGridWaitForAnimation(agGridElement);
- return _getAgGrid(agGridElement, options, false);
+ return extractAgGridData(getSingleAgGridRootElement(agGridElement), options);
};
/**
@@ -102,127 +44,11 @@ export const getAgGridData = async (agGridElement, options = {}) => {
* @param options Provide an array of columns you wish to exclude from the table retrieval.
*/
export const getAgGridElements = async (agGridElement, options = {}) => {
- await agGridWaitForAnimation(agGridElement);
- return _getAgGrid(agGridElement, options, true);
-};
-
-function _getAgGrid(agGridElement, options = {}, returnElements) {
- const agGridColumnSelectors =
- ".ag-pinned-left-cols-container^.ag-center-cols-clipper^.ag-center-cols-viewport^.ag-pinned-right-cols-container";
- if (agGridElement.get().length > 1)
- throw new Error(
- `Selector "${agGridElement.selector}" returned more than 1 element.`
- );
-
- const tableElement = agGridElement.get()[0].querySelectorAll(".ag-root")[0];
- const agGridSelectors = agGridColumnSelectors.split("^");
- const headers = [
- ...tableElement.querySelectorAll(".ag-header-row-column [aria-colindex]"),
- ]
- .sort(sortElementsByAttributeValue("aria-colindex"))
- .map((headerElement) => {
- // Check if the elements returned are already .ag-header-cell-text elements
- // If not, query for that element and return the text content
- let headerCells = [
- ...headerElement.querySelectorAll(".ag-header-cell-text"),
- ];
- if (headerCells.length === 0) {
- return [headerElement].map((e) => e.textContent.trim());
- } else {
- return [...headerElement.querySelectorAll(".ag-header-cell-text")].map(
- (e) => e.textContent.trim()
- );
- }
- })
- .flat();
-
- let allRows = [];
- let rows = [];
-
- agGridSelectors.forEach((selector) => {
- const _rows = [
- ...tableElement.querySelectorAll(
- `${selector}:not(.ag-hidden) .ag-row:not(.ag-opacity-zero)`
- ),
- ]
- // When animation is enabled, ag-grid destroys rows in 2 phases,
- // first it runs an animation to place rows to be destroyed just outside
- // the viewport.
- // In the second phase those rows are removed from the DOM.
- // Because we get here AFTER all animations are finished, it is possible,
- // those rows are still in the DOM, but are not visible.
- // therefore those rows should be filtered out.
- .filter(isRowNotDestroyed)
- // Sort rows by their row-index attribute value
- .sort(sortElementsByAttributeValue("row-index"))
- .map((row) => {
- // Sort row cells by their aria-colindex attribute value
- // First check if elements returned already contain the aria-colindex
- // If not, just query for the .ag-cell
- let rowCells = [...row.querySelectorAll(".ag-cell[aria-colindex]")];
- if (rowCells.length === 0) {
- rowCells = [...row.querySelectorAll(".ag-cell")];
- }
- const rowIndex = parseInt(
- row.attributes["row-index"].nodeValue,
- 10
- ).valueOf();
-
- if (allRows[rowIndex]) {
- allRows[rowIndex] = [...allRows[rowIndex], ...rowCells];
- } else {
- allRows[rowIndex] = rowCells;
- }
- });
- });
- // Remove any empty arrays before merging
- allRows = allRows.filter(function (ele) {
- return ele.length;
- });
-
- // Remove duplicate entries from allRows
- // In some instances we see cell duplication for non-unique rows
- allRows = allRows.map((row) => {
- return row.filter((cell, index) => {
- return row.indexOf(cell) === index;
- });
- });
-
- if (!allRows.length) rows = [];
- else {
- rows = allRows
- .filter((rowCells) => rowCells.length)
- .map((rowCells) =>
- rowCells
- .sort(sortElementsByAttributeValue("aria-colindex"))
- .map((e) => {
- if (returnElements) {
- return e;
- } else {
- return e.textContent.trim();
- }
- })
- );
- }
- // if options.rawValues = true, return headers & rows values as arrays instead of mapping as objects
- if (options.valuesArray) {
- return { headers, rows };
- }
- // return structured object from headers and rows variables
- return rows.map((row) =>
- row.reduce((acc, curr, idx) => {
- if (
- //@ts-ignore
- (options.onlyColumns && !options.onlyColumns.includes(headers[idx])) ||
- headers[idx] === undefined
- ) {
- // dont include columns that are not present in onlyColumns, or if the header is undefined
- return { ...acc };
- }
- return { ...acc, [headers[idx]]: curr };
- }, {})
+ return extractAgGridElements(
+ getSingleAgGridRootElement(agGridElement),
+ options
);
-}
+};
/**
* Retrieve the ag grid column header element based on its column name value
diff --git a/src/agGrid/agGridValidations.js b/packages/cypress-ag-grid/src/agGrid/agGridValidations.js
similarity index 100%
rename from src/agGrid/agGridValidations.js
rename to packages/cypress-ag-grid/src/agGrid/agGridValidations.js
diff --git a/src/agGrid/filterOperator.enum.js b/packages/cypress-ag-grid/src/agGrid/filterOperator.enum.js
similarity index 100%
rename from src/agGrid/filterOperator.enum.js
rename to packages/cypress-ag-grid/src/agGrid/filterOperator.enum.js
diff --git a/src/agGrid/menuTab.enum.js b/packages/cypress-ag-grid/src/agGrid/menuTab.enum.js
similarity index 100%
rename from src/agGrid/menuTab.enum.js
rename to packages/cypress-ag-grid/src/agGrid/menuTab.enum.js
diff --git a/src/agGrid/sort.enum.js b/packages/cypress-ag-grid/src/agGrid/sort.enum.js
similarity index 100%
rename from src/agGrid/sort.enum.js
rename to packages/cypress-ag-grid/src/agGrid/sort.enum.js
diff --git a/src/helpers/arrayHelpers.js b/packages/cypress-ag-grid/src/helpers/arrayHelpers.js
similarity index 100%
rename from src/helpers/arrayHelpers.js
rename to packages/cypress-ag-grid/src/helpers/arrayHelpers.js
diff --git a/src/index.d.ts b/packages/cypress-ag-grid/src/index.d.ts
similarity index 100%
rename from src/index.d.ts
rename to packages/cypress-ag-grid/src/index.d.ts
diff --git a/src/index.js b/packages/cypress-ag-grid/src/index.js
similarity index 100%
rename from src/index.js
rename to packages/cypress-ag-grid/src/index.js
diff --git a/packages/playwright-ag-grid/README.md b/packages/playwright-ag-grid/README.md
new file mode 100644
index 0000000..cc1233a
--- /dev/null
+++ b/packages/playwright-ag-grid/README.md
@@ -0,0 +1,387 @@
+# playwright-ag-grid
+
+Playwright helpers for interacting with and validating AG Grid.
+
+## Table of Contents
+
+- [Installation](#installation)
+- [Usage](#usage)
+ - [Create the Grid Helper](#create-the-grid-helper)
+ - [Getting Data From the Grid](#getting-data-from-the-grid)
+ - [Getting Select Row Data](#getting-select-row-data)
+ - [Editing Grid Cells](#editing-grid-cells)
+ - [Sorting Columns](#sorting-columns)
+ - [Pinning Columns](#pinning-columns)
+ - [Filter Options](#filter-options)
+ - [Filter by Text - Column Menu](#filter-by-text---column-menu)
+ - [Filter by Text - Floating Filter](#filter-by-text---floating-filter)
+ - [Filter by Checkbox - Column Menu](#filter-by-checkbox---column-menu)
+ - [Filtering - Localization and Internationalization](#filtering---localization-and-internationalization)
+ - [Add or Remove Columns](#add-or-remove-columns)
+ - [Validation Examples](#validation-examples)
+ - [Validate Paginated Table](#validate-paginated-table)
+ - [Validate Table in the Exact Order](#validate-table-in-the-exact-order)
+ - [Validate Subset of Table Data](#validate-subset-of-table-data)
+ - [Validate Empty Grid](#validate-empty-grid)
+- [Limitations](#limitations)
+
+## Installation
+
+```bash
+npm install playwright-ag-grid --save-dev
+```
+
+Then import the helper in your Playwright tests:
+
+```javascript
+import { createAgGrid, filterOperator, sort } from "playwright-ag-grid";
+```
+
+## Usage
+
+### Create the Grid Helper
+
+Create a helper instance from the top-level AG Grid locator, including headers and controls.
+
+```javascript
+import { createAgGrid } from "playwright-ag-grid";
+
+const grid = createAgGrid(page.locator("#myGrid"));
+```
+
+### Getting Data From the Grid
+
+Use `getData()` to read the displayed AG Grid rows as structured objects.
+
+```javascript
+const grid = createAgGrid(page.locator("#myGrid"));
+const tableData = await grid.getData();
+```
+
+The returned value looks like:
+
+```json
+[
+ { "Year": "2020", "Make": "Toyota", "Model": "Celica" },
+ { "Year": "2020", "Make": "Ford", "Model": "Mondeo" },
+ { "Year": "2020", "Make": "Porsche", "Model": "Boxter" },
+ { "Year": "2020", "Make": "BMW", "Model": "3-series" },
+ { "Year": "2020", "Make": "Mercedes", "Model": "GLC300" }
+]
+```
+
+### Getting Select Row Data
+
+To only return certain columns, pass `onlyColumns`:
+
+```javascript
+const grid = createAgGrid(page.locator("#myGrid"));
+const tableData = await grid.getData({ onlyColumns: ["Year", "Make"] });
+```
+
+You can also request the raw header/row arrays instead of mapped objects:
+
+```javascript
+const grid = createAgGrid(page.locator("#myGrid"));
+const tableData = await grid.getData({ valuesArray: true });
+```
+
+### Editing Grid Cells
+
+Playwright does not expose AG Grid cells as Cypress-style command subjects, so the package provides `getCellLocator()` for targeted cell interaction.
+
+```javascript
+const grid = createAgGrid(page.locator("#myGrid2"));
+
+await grid.filterTextFloating({
+ searchCriteria: {
+ columnName: "Make",
+ filterValue: "Porsche",
+ operator: filterOperator.equals,
+ },
+ hasApplyButton: true,
+});
+
+const priceCell = await grid.getCellLocator(
+ { Make: "Porsche", Price: "72000" },
+ "Price"
+);
+
+await priceCell.dblclick();
+await priceCell.locator("input").fill("66000");
+await priceCell.locator("input").press("Enter");
+```
+
+### Sorting Columns
+
+Use `sortColumn(columnName, sortDirection)`:
+
+```javascript
+const grid = createAgGrid(page.locator("#myGrid"));
+await grid.sortColumn("Model", "descending");
+```
+
+You can also use the exported enum values:
+
+```javascript
+await grid.sortColumn("Model", sort.ascending);
+```
+
+### Pinning Columns
+
+Use `pinColumn(columnName, pin)` where `pin` is `"left"`, `"right"`, or `null`.
+
+```javascript
+const grid = createAgGrid(page.locator("#myGrid"));
+
+await grid.pinColumn("Model", "left");
+await grid.pinColumn("Model", "right");
+await grid.pinColumn("Model", null);
+```
+
+### Filter Options
+
+The filtering commands accept an options object shaped like:
+
+```javascript
+{
+ searchCriteria: {
+ columnName: string;
+ filterValue: string;
+ operator?: string;
+ searchInputIndex?: number;
+ operatorIndex?: number;
+ isMultiFilter?: boolean;
+ } | Array<{
+ columnName: string;
+ filterValue: string;
+ operator?: string;
+ searchInputIndex?: number;
+ operatorIndex?: number;
+ isMultiFilter?: boolean;
+ }>;
+ hasApplyButton?: boolean;
+ noMenuTabs?: boolean;
+ selectAllLocaleText?: string;
+}
+```
+
+Option notes:
+
+- `searchCriteria.columnName`: column to filter.
+- `searchCriteria.filterValue`: value to type or select.
+- `searchCriteria.operator`: optional AG Grid operator text such as `Equals`, `Contains`, or `Between`.
+- `searchCriteria.searchInputIndex`: optional input index when multiple visible inputs exist.
+- `searchCriteria.operatorIndex`: optional operator picker index when multiple visible operators exist.
+- `searchCriteria.isMultiFilter`: optional flag for floating filters using checkbox values rather than free-form text.
+- `hasApplyButton`: use `true` when the filter UI has an explicit Apply button.
+- `noMenuTabs`: use `true` for grids that render a menu list instead of filter tabs.
+- `selectAllLocaleText`: localized text for the checkbox filter's Select All entry.
+
+### Filter by Text - Column Menu
+
+Use `filterTextMenu(options)` to filter via the column menu:
+
+```javascript
+const grid = createAgGrid(page.locator("#myGrid"));
+
+await grid.filterTextMenu({
+ searchCriteria: [
+ {
+ columnName: "Model",
+ filterValue: "GLC300",
+ operator: filterOperator.equals,
+ },
+ {
+ columnName: "Make",
+ filterValue: "Mercedes",
+ operator: filterOperator.equals,
+ },
+ ],
+ hasApplyButton: true,
+});
+```
+
+### Filter by Text - Floating Filter
+
+Use `filterTextFloating(options)` to filter via a column's floating filter:
+
+```javascript
+const grid = createAgGrid(page.locator("#myGrid"));
+
+await grid.filterTextFloating({
+ searchCriteria: {
+ columnName: "Make",
+ filterValue: "Ford",
+ },
+ hasApplyButton: true,
+});
+```
+
+For multiple conditions in the same floating filter:
+
+```javascript
+await grid.filterTextFloating({
+ searchCriteria: {
+ columnName: "Make",
+ filterValue: "B",
+ searchInputIndex: 0,
+ },
+ hasApplyButton: true,
+});
+
+await grid.filterTextFloating({
+ searchCriteria: {
+ columnName: "Make",
+ filterValue: "MW",
+ searchInputIndex: 1,
+ },
+ hasApplyButton: true,
+});
+```
+
+For `Between`, pass two entries for the same column:
+
+```javascript
+await grid.filterTextFloating({
+ searchCriteria: [
+ {
+ columnName: "Year",
+ filterValue: "1990",
+ operator: filterOperator.inRange,
+ },
+ {
+ columnName: "Year",
+ filterValue: "2011",
+ operator: filterOperator.inRange,
+ },
+ ],
+ hasApplyButton: true,
+});
+```
+
+### Filter by Checkbox - Column Menu
+
+Use `filterCheckboxMenu(options)` to filter by checkbox values from the column menu:
+
+```javascript
+const grid = createAgGrid(page.locator("#myGrid"));
+
+await grid.filterCheckboxMenu({
+ searchCriteria: {
+ columnName: "Model",
+ filterValue: "2002",
+ },
+ hasApplyButton: true,
+});
+```
+
+Multiple checkbox values:
+
+```javascript
+await grid.filterCheckboxMenu({
+ searchCriteria: [
+ { columnName: "Model", filterValue: "2002" },
+ { columnName: "Model", filterValue: "3-series" },
+ ],
+ hasApplyButton: true,
+});
+```
+
+### Filtering - Localization and Internationalization
+
+When filtering by checkbox, the helper first deselects the Select All entry so only the requested values remain selected. For localized grids, pass the localized Select All text:
+
+```javascript
+await grid.filterCheckboxMenu({
+ searchCriteria: {
+ columnName: "Model",
+ filterValue: "2002",
+ },
+ selectAllLocaleText: "Tout Sélectionner",
+ hasApplyButton: true,
+});
+```
+
+### Add or Remove Columns
+
+Use `toggleColumnFromSideBar(columnName, doRemove)` to toggle columns from the sidebar:
+
+```javascript
+const grid = createAgGrid(page.locator("#myGrid"));
+
+await grid.toggleColumnFromSideBar("Year", true);
+await grid.toggleColumnFromSideBar("Year", false);
+```
+
+## Validation Examples
+
+Unlike the Cypress package, the Playwright package does not currently register assertion helpers. The intended pattern is to use `await grid.getData()` together with Playwright's `expect`.
+
+### Validate Paginated Table
+
+```javascript
+import { expect } from "@playwright/test";
+import { createAgGrid } from "playwright-ag-grid";
+
+const expectedPaginatedTableData = [
+ [
+ { Year: "2020", Make: "Toyota", Model: "Celica" },
+ { Year: "2020", Make: "Ford", Model: "Mondeo" },
+ ],
+ [
+ { Year: "2020", Make: "Honda", Model: "Civic" },
+ { Year: "2020", Make: "Honda", Model: "Accord" },
+ ],
+];
+
+const grid = createAgGrid(page.locator("#myGrid"));
+
+for (const expectedPage of expectedPaginatedTableData) {
+ await expect(await grid.getData({ onlyColumns: ["Year", "Make", "Model"] }))
+ .toEqual(expectedPage);
+ await page.locator("#myGrid .ag-icon-next").click();
+}
+```
+
+### Validate Table in the Exact Order
+
+```javascript
+import { expect } from "@playwright/test";
+
+const grid = createAgGrid(page.locator("#myGrid"));
+const actualTableData = await grid.getData();
+
+await expect(actualTableData).toEqual(expectedTableData);
+```
+
+### Validate Subset of Table Data
+
+```javascript
+import { expect } from "@playwright/test";
+
+const grid = createAgGrid(page.locator("#myGrid"));
+const actualTableData = await grid.getData({ onlyColumns: ["Year", "Make", "Model"] });
+
+for (const expectedRow of expectedTableData) {
+ expect(actualTableData).toContainEqual(expectedRow);
+}
+```
+
+### Validate Empty Grid
+
+```javascript
+import { expect } from "@playwright/test";
+
+const grid = createAgGrid(page.locator("#myGrid"));
+const actualTableData = await grid.getData();
+
+await expect(actualTableData).toEqual([]);
+```
+
+## Limitations
+
+- Validation helpers are not yet packaged as Playwright-specific assertion methods; use Playwright `expect` with `getData()`.
+- `getCellLocator()` is the supported element-interaction path; there is not currently a `getAgGridElements()`-style API returning live DOM element maps.
+- Unlimited scrolling grids are not fully supported when data is outside the rendered DOM.
+- Data outside the current rendered viewport is not available until AG Grid renders it into the DOM.
diff --git a/packages/playwright-ag-grid/package.json b/packages/playwright-ag-grid/package.json
new file mode 100644
index 0000000..04e7be4
--- /dev/null
+++ b/packages/playwright-ag-grid/package.json
@@ -0,0 +1,41 @@
+{
+ "name": "playwright-ag-grid",
+ "version": "0.1.0",
+ "description": "Playwright helpers for interacting with AG Grid",
+ "type": "module",
+ "main": "src/index.js",
+ "types": "src/index.d.ts",
+ "exports": {
+ ".": "./src/index.js"
+ },
+ "scripts": {
+ "test": "npm run test:all",
+ "test:all": "playwright test",
+ "test:v33": "playwright test tests/ag-grid-data.v33.spec.js tests/ag-grid-elements.v33.spec.js",
+ "test:v34": "playwright test tests/ag-grid-data.v34.spec.js tests/ag-grid-elements.v34.spec.js",
+ "test:v35": "playwright test tests/ag-grid-data.v35.spec.js tests/ag-grid-elements.v35.spec.js tests/ag-grid-animation-wait.v35.spec.js",
+ "test:watch": "playwright test --ui"
+ },
+ "publishConfig": {
+ "access": "public"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/kpmck/cypress-ag-grid.git"
+ },
+ "keywords": [
+ "ag-grid",
+ "aggrid",
+ "playwright",
+ "testing",
+ "e2e"
+ ],
+ "author": "Kerry McKeever ",
+ "license": "MIT",
+ "dependencies": {
+ "@kpmck/ag-grid-core": "0.1.0"
+ },
+ "devDependencies": {
+ "@playwright/test": "^1.52.0"
+ }
+}
diff --git a/packages/playwright-ag-grid/playwright.config.js b/packages/playwright-ag-grid/playwright.config.js
new file mode 100644
index 0000000..0653717
--- /dev/null
+++ b/packages/playwright-ag-grid/playwright.config.js
@@ -0,0 +1,21 @@
+import path from "node:path";
+import { fileURLToPath } from "node:url";
+import { defineConfig } from "@playwright/test";
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+
+export default defineConfig({
+ testDir: path.join(__dirname, "tests"),
+ timeout: 30000,
+ use: {
+ baseURL: "http://127.0.0.1:4173",
+ headless: true,
+ },
+ webServer: {
+ command: "node ./tests/server.mjs",
+ cwd: __dirname,
+ port: 4173,
+ reuseExistingServer: true,
+ },
+});
diff --git a/packages/playwright-ag-grid/src/index.d.ts b/packages/playwright-ag-grid/src/index.d.ts
new file mode 100644
index 0000000..ca83ecc
--- /dev/null
+++ b/packages/playwright-ag-grid/src/index.d.ts
@@ -0,0 +1,33 @@
+export declare const filterOperator: Record;
+export declare const sort: Record;
+
+export interface SearchCriteria {
+ columnName: string;
+ filterValue: string;
+ operator?: string;
+ searchInputIndex?: number;
+ operatorIndex?: number;
+ isMultiFilter?: boolean;
+}
+
+export interface FilterOptions {
+ searchCriteria: SearchCriteria | SearchCriteria[];
+ hasApplyButton?: boolean;
+ noMenuTabs?: boolean;
+ selectAllLocaleText?: string;
+}
+
+export declare class PlaywrightAgGrid {
+ constructor(rootLocator: any);
+ waitForAnimation(options?: object): Promise;
+ getData(options?: object): Promise;
+ sortColumn(columnName: string, sortDirection: string): Promise;
+ pinColumn(columnName: string, pin?: "left" | "right" | null): Promise;
+ filterTextMenu(options: FilterOptions): Promise;
+ filterTextFloating(options: FilterOptions): Promise;
+ filterCheckboxMenu(options: FilterOptions): Promise;
+ toggleColumnFromSideBar(columnName: string, doRemove: boolean): Promise;
+ getCellLocator(rowMatcher: Record, columnName: string): Promise;
+}
+
+export declare function createAgGrid(rootLocator: any): PlaywrightAgGrid;
diff --git a/packages/playwright-ag-grid/src/index.js b/packages/playwright-ag-grid/src/index.js
new file mode 100644
index 0000000..3a2b81b
--- /dev/null
+++ b/packages/playwright-ag-grid/src/index.js
@@ -0,0 +1,393 @@
+import {
+ browserExtractAgGrid,
+ browserWaitForAgGridAnimation,
+ filterOperator,
+ filterTab,
+ sort,
+} from "@kpmck/ag-grid-core";
+
+function escapeRegExp(value) {
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
+}
+
+async function getHeaderTextLocator(rootLocator, columnName) {
+ return rootLocator
+ .locator(".ag-header-cell-text")
+ .filter({ hasText: new RegExp(`^${escapeRegExp(columnName)}$`) })
+ .first();
+}
+
+async function getColumnHeaderMeta(rootLocator, columnName) {
+ const metadata = await rootLocator.evaluate((root, targetColumnName) => {
+ const headerTexts = [...root.querySelectorAll(".ag-header-cell-text")];
+ const matchingHeaderText = headerTexts.find(
+ (element) => element.textContent.trim() === targetColumnName
+ );
+
+ if (!matchingHeaderText) {
+ return null;
+ }
+
+ const headerCell = matchingHeaderText.closest(".ag-header-cell");
+ const visibleHeaderCells = [...root.querySelectorAll(".ag-header-row-column .ag-header-cell")]
+ .filter((element) => element.offsetParent !== null);
+ const headerPosition = headerCell
+ ? visibleHeaderCells.indexOf(headerCell)
+ : -1;
+
+ return {
+ columnIndex: headerCell?.getAttribute("aria-colindex") ?? null,
+ headerPosition,
+ };
+ }, columnName);
+
+ if (!metadata) {
+ throw new Error(`Unable to find AG Grid column "${columnName}".`);
+ }
+
+ return metadata;
+}
+
+async function getHeaderCellLocator(rootLocator, columnName) {
+ const { columnIndex } = await getColumnHeaderMeta(rootLocator, columnName);
+ if (!columnIndex) {
+ throw new Error(`Unable to resolve a header cell for "${columnName}".`);
+ }
+
+ return rootLocator.locator(`.ag-header-row-column .ag-header-cell[aria-colindex="${columnIndex}"]`).first();
+}
+
+async function getMenuTabLocator(rootLocator, tabName) {
+ return rootLocator.locator(".ag-tab").locator(`.ag-icon-${tabName}`).locator("xpath=ancestor::span[1]").first();
+}
+
+async function maybeCloseMenuTab(rootLocator, noMenuTabs = false) {
+ if (noMenuTabs) {
+ return;
+ }
+
+ const tabs = rootLocator.locator(".ag-tab");
+ if ((await tabs.count()) === 0) {
+ await rootLocator.evaluate(browserWaitForAgGridAnimation);
+ return;
+ }
+
+ await (await getMenuTabLocator(rootLocator, filterTab.filter)).click();
+}
+
+async function getFloatingFilterButton(rootLocator, columnName) {
+ const { columnIndex, headerPosition } = await getColumnHeaderMeta(rootLocator, columnName);
+
+ const usesV35FloatingFilterRow =
+ (await rootLocator.locator(".ag-header-row-filter").count()) > 0;
+
+ let buttonLocator = usesV35FloatingFilterRow
+ ? rootLocator.locator(
+ `.ag-header-row-filter .ag-header-cell[aria-colindex="${columnIndex}"] .ag-floating-filter-button:visible`
+ )
+ : rootLocator.locator(
+ `.ag-header-row-column-filter .ag-header-cell[aria-colindex="${columnIndex}"] .ag-floating-filter-button-button:visible`
+ );
+
+ if ((await buttonLocator.count()) === 0 && headerPosition > -1) {
+ buttonLocator = usesV35FloatingFilterRow
+ ? rootLocator.locator(".ag-header-row-filter .ag-floating-filter-button:visible").nth(headerPosition)
+ : rootLocator
+ .locator(".ag-header-row-column-filter .ag-floating-filter-button-button:visible")
+ .nth(headerPosition);
+ } else {
+ buttonLocator = buttonLocator.first();
+ }
+
+ return buttonLocator;
+}
+
+async function getFilterColumnButton(rootLocator, columnName, isFloatingFilter = false) {
+ if (isFloatingFilter) {
+ return getFloatingFilterButton(rootLocator, columnName);
+ }
+
+ const headerCell = await getHeaderCellLocator(rootLocator, columnName);
+ await headerCell.hover();
+ return headerCell.locator(".ag-header-cell-filter-button").first();
+}
+
+async function toggleColumnCheckboxFilter(rootLocator, filterValue, doSelect) {
+ const label = rootLocator
+ .page()
+ .locator(".ag-popup .ag-input-field-label:visible")
+ .filter({
+ hasText: new RegExp(`^${escapeRegExp(filterValue)}$`),
+ })
+ .first();
+ await label.waitFor({ state: "visible" });
+ const toggle = label.locator("xpath=following-sibling::div[1]").first();
+ const checkbox = label.locator("xpath=following-sibling::div[1]//input").first();
+
+ const isChecked = await checkbox.isChecked();
+ if (isChecked !== doSelect) {
+ await toggle.click({ force: true });
+ }
+}
+
+async function filterBySearchTerm(rootLocator, options) {
+ const filterValue = options.searchCriteria.filterValue;
+ const operator = options.searchCriteria.operator;
+ const searchInputIndex = options.searchCriteria.searchInputIndex || 0;
+ const operatorIndex =
+ options.searchCriteria.operatorIndex ??
+ (operator === filterOperator.inRange ? 0 : searchInputIndex);
+ const isMultiFilter = options.searchCriteria.isMultiFilter;
+
+ if (operator) {
+ const picker = rootLocator
+ .locator(".ag-filter .ag-picker-field-wrapper:visible")
+ .nth(operatorIndex);
+ await rootLocator.evaluate(browserWaitForAgGridAnimation);
+ await picker.click();
+ await rootLocator.locator(".ag-popup .ag-list span").filter({
+ hasText: new RegExp(`^${escapeRegExp(operator)}$`),
+ }).first().click();
+ }
+
+ if (isMultiFilter) {
+ const selectAllText = options.selectAllLocaleText || "(Select All)";
+ await toggleColumnCheckboxFilter(rootLocator, selectAllText, false);
+ const miniFilterInput = rootLocator
+ .page()
+ .locator(".ag-popup-child input:not([type='radio']):not([type='checkbox']):visible")
+ .first();
+ if (await miniFilterInput.count()) {
+ await miniFilterInput.fill("");
+ await miniFilterInput.type(`${filterValue}`);
+ }
+ }
+
+ if (
+ !isMultiFilter &&
+ operator !== filterOperator.blank &&
+ operator !== filterOperator.notBlank
+ ) {
+ const input = rootLocator
+ .locator(".ag-popup-child input:not([type='radio']):not([type='checkbox']):visible")
+ .nth(searchInputIndex);
+ await input.fill("");
+ await input.type(`${filterValue}`);
+ await input.press("Enter");
+ }
+
+ if (isMultiFilter) {
+ await toggleColumnCheckboxFilter(rootLocator, filterValue, true);
+ }
+}
+
+function normalizeFloatingFilterSearchCriteria(searchCriteria) {
+ const betweenInputIndexes = new Map();
+
+ return searchCriteria.map((criteria) => {
+ if (
+ criteria.operator !== filterOperator.inRange ||
+ criteria.searchInputIndex !== undefined
+ ) {
+ return criteria;
+ }
+
+ const criteriaKey = `${criteria.columnName}::${criteria.operator}`;
+ const nextInputIndex = betweenInputIndexes.get(criteriaKey) || 0;
+ betweenInputIndexes.set(criteriaKey, nextInputIndex + 1);
+
+ return { ...criteria, searchInputIndex: nextInputIndex };
+ });
+}
+
+function groupFloatingFilterSearchCriteria(searchCriteria) {
+ const groupedCriteria = [];
+
+ searchCriteria.forEach((criteria) => {
+ const lastGroup = groupedCriteria[groupedCriteria.length - 1];
+
+ if (
+ criteria.operator === filterOperator.inRange &&
+ lastGroup &&
+ lastGroup[0].columnName === criteria.columnName &&
+ lastGroup[0].operator === criteria.operator
+ ) {
+ lastGroup.push(criteria);
+ return;
+ }
+
+ groupedCriteria.push([criteria]);
+ });
+
+ return groupedCriteria;
+}
+
+async function applyColumnFilter(rootLocator, hasApplyButton, noMenuTabs) {
+ if (hasApplyButton) {
+ await rootLocator.locator(".ag-filter-apply-panel-button").getByText("Apply", { exact: true }).click();
+ }
+
+ await maybeCloseMenuTab(rootLocator, noMenuTabs);
+ await rootLocator.page().keyboard.press("Escape");
+}
+
+export class PlaywrightAgGrid {
+ constructor(rootLocator) {
+ this.rootLocator = rootLocator;
+ }
+
+ async waitForAnimation(options = {}) {
+ await this.rootLocator.evaluate(browserWaitForAgGridAnimation, options);
+ }
+
+ async getData(options = {}) {
+ await this.rootLocator.evaluate(browserWaitForAgGridAnimation, options);
+ return this.rootLocator.evaluate(browserExtractAgGrid, options);
+ }
+
+ async sortColumn(columnName, sortDirection) {
+ let normalized = sortDirection;
+
+ if (normalized.toLowerCase() === "ascending") {
+ normalized = "asc";
+ } else if (normalized.toLowerCase() === "descending") {
+ normalized = "desc";
+ }
+
+ if (normalized !== sort.ascending && normalized !== sort.descending) {
+ throw new Error("sortDirection must be either 'asc' or 'desc'.");
+ }
+
+ const headerText = await getHeaderTextLocator(this.rootLocator, columnName);
+ const container = headerText.locator(
+ "xpath=ancestor::*[contains(@class, 'ag-cell-label-container')][1]"
+ );
+
+ for (let attempts = 0; attempts < 3; attempts += 1) {
+ const className = (await container.getAttribute("class")) || "";
+ if (className.includes(`ag-header-cell-sorted-${normalized}`)) {
+ return;
+ }
+ await headerText.click();
+ }
+ }
+
+ async pinColumn(columnName, pin) {
+ const headerCell = await getHeaderCellLocator(this.rootLocator, columnName);
+ await headerCell.hover();
+ await headerCell.locator(".ag-header-cell-menu-button").click();
+ if ((await this.rootLocator.locator(".ag-tab").count()) > 0) {
+ await (await getMenuTabLocator(this.rootLocator, filterTab.general)).click();
+ }
+ await this.rootLocator.locator(".ag-menu-option").getByText("Pin Column", { exact: true }).click();
+
+ const selectedOption =
+ pin === "left" ? "Pin Left" : pin === "right" ? "Pin Right" : "No Pin";
+
+ await this.rootLocator.locator(".ag-menu-option").getByText(selectedOption, { exact: true }).click();
+ }
+
+ async filterTextMenu(options) {
+ const criteriaList = Array.isArray(options.searchCriteria)
+ ? options.searchCriteria
+ : [options.searchCriteria];
+
+ for (const searchCriteria of criteriaList) {
+ const optionSet = { ...options, searchCriteria };
+ await (await getFilterColumnButton(this.rootLocator, searchCriteria.columnName)).click();
+ await filterBySearchTerm(this.rootLocator, optionSet);
+ await applyColumnFilter(this.rootLocator, options.hasApplyButton, options.noMenuTabs);
+ }
+ }
+
+ async filterTextFloating(options) {
+ const criteriaList = Array.isArray(options.searchCriteria)
+ ? normalizeFloatingFilterSearchCriteria(options.searchCriteria)
+ : [options.searchCriteria];
+
+ const groups = groupFloatingFilterSearchCriteria(criteriaList);
+
+ for (const group of groups) {
+ await (await getFilterColumnButton(this.rootLocator, group[0].columnName, true)).click();
+
+ for (let index = 0; index < group.length; index += 1) {
+ const criteria = group[index];
+ await filterBySearchTerm(this.rootLocator, {
+ ...options,
+ searchCriteria: index === 0 ? criteria : { ...criteria, operator: undefined },
+ });
+ }
+
+ await applyColumnFilter(this.rootLocator, options.hasApplyButton, options.noMenuTabs);
+ }
+ }
+
+ async filterCheckboxMenu(options) {
+ const criteriaList = Array.isArray(options.searchCriteria)
+ ? options.searchCriteria
+ : [options.searchCriteria];
+
+ for (const searchCriteria of criteriaList) {
+ await (await getFilterColumnButton(this.rootLocator, searchCriteria.columnName)).click();
+ await toggleColumnCheckboxFilter(
+ this.rootLocator,
+ options.selectAllLocaleText || "(Select All)",
+ false
+ );
+ await toggleColumnCheckboxFilter(this.rootLocator, searchCriteria.filterValue, true);
+ await applyColumnFilter(this.rootLocator, options.hasApplyButton, options.noMenuTabs);
+ }
+ }
+
+ async toggleColumnFromSideBar(columnName, doRemove) {
+ const columnFilterInput = this.rootLocator.locator(
+ ".ag-column-select-header-filter-wrapper input"
+ ).first();
+
+ if (!(await columnFilterInput.isVisible())) {
+ await this.rootLocator.page().locator(".ag-side-buttons span").getByText("Columns", { exact: true }).click();
+ }
+
+ await this.waitForAnimation();
+ await columnFilterInput.fill(columnName);
+ const checkbox = this.rootLocator.page()
+ .locator(".ag-column-select-column-label")
+ .filter({ hasText: new RegExp(`^${escapeRegExp(columnName)}$`) })
+ .first()
+ .locator("xpath=ancestor::*[1]//input")
+ .first();
+
+ if (doRemove) {
+ await checkbox.uncheck({ force: true });
+ } else {
+ await checkbox.check({ force: true });
+ }
+ }
+
+ async getCellLocator(rowMatcher, columnName) {
+ const rows = await this.getData();
+ const rowIndex = rows.findIndex((row) =>
+ Object.entries(rowMatcher).every(([key, value]) => row[key] === value)
+ );
+
+ if (rowIndex === -1) {
+ throw new Error(`Unable to find row matching ${JSON.stringify(rowMatcher)}.`);
+ }
+
+ const valuesArray = await this.getData({ valuesArray: true });
+ const columnIndex = valuesArray.headers.indexOf(columnName);
+
+ if (columnIndex === -1) {
+ throw new Error(`Unable to find column "${columnName}".`);
+ }
+
+ const visibleRows = this.rootLocator.locator(".ag-center-cols-clipper .ag-row:not(.ag-opacity-zero), .ag-center-cols-viewport .ag-row:not(.ag-opacity-zero)");
+ return visibleRows.nth(rowIndex).locator(".ag-cell").nth(columnIndex);
+ }
+}
+
+export function createAgGrid(rootLocator) {
+ return new PlaywrightAgGrid(rootLocator);
+}
+
+export { filterOperator, sort };
diff --git a/packages/playwright-ag-grid/tests/ag-grid-animation-wait.v35.spec.js b/packages/playwright-ag-grid/tests/ag-grid-animation-wait.v35.spec.js
new file mode 100644
index 0000000..97e04a6
--- /dev/null
+++ b/packages/playwright-ag-grid/tests/ag-grid-animation-wait.v35.spec.js
@@ -0,0 +1,52 @@
+import { expect, test } from "@playwright/test";
+
+import { createAgGrid } from "../src/index.js";
+
+test.describe("agGridWaitForAnimation", () => {
+ test("waits for AG Grid-owned animations to finish", async ({ page }) => {
+ await page.goto("/animation-wait/ag-owned.html");
+ await page.locator(".ag-cell").first().waitFor({ state: "visible" });
+
+ await page.evaluate(() => {
+ window.startAnimationWaitScenario();
+ window.__animationProbe.waitStartedAt = Date.now();
+ });
+
+ const grid = createAgGrid(page.locator("#myGrid"));
+ await grid.waitForAnimation();
+
+ const probe = await page.evaluate(() => ({
+ ...window.__animationProbe,
+ elapsedMs: Date.now() - window.__animationProbe.waitStartedAt,
+ }));
+
+ expect(probe.agStarted).toBe(true);
+ expect(probe.agFinished).toBe(true);
+ expect(probe.elapsedMs).toBeGreaterThan(200);
+ expect(probe.elapsedMs).toBeLessThan(2000);
+ });
+
+ test("ignores third-party subtree animations whose finished promise never resolves", async ({ page }) => {
+ await page.goto("/animation-wait/third-party-subtree.html");
+ await page.locator(".ag-cell").first().waitFor({ state: "visible" });
+
+ await page.evaluate(() => {
+ window.startAnimationWaitScenario();
+ window.__animationProbe.waitStartedAt = Date.now();
+ });
+
+ expect(await page.locator("#myGrid .os-scrollbar-handle").count()).toBeGreaterThan(0);
+
+ const grid = createAgGrid(page.locator("#myGrid"));
+ await grid.waitForAnimation();
+
+ const probe = await page.evaluate(() => ({
+ ...window.__animationProbe,
+ elapsedMs: Date.now() - window.__animationProbe.waitStartedAt,
+ }));
+
+ expect(probe.agFinished).toBe(true);
+ expect(probe.thirdPartyInstalled).toBe(true);
+ expect(probe.elapsedMs).toBeLessThan(2000);
+ });
+});
diff --git a/packages/playwright-ag-grid/tests/ag-grid-data.v33.spec.js b/packages/playwright-ag-grid/tests/ag-grid-data.v33.spec.js
new file mode 100644
index 0000000..4fbdf55
--- /dev/null
+++ b/packages/playwright-ag-grid/tests/ag-grid-data.v33.spec.js
@@ -0,0 +1,6 @@
+import { runAgGridDataSuite } from "./shared/run-ag-grid-data-suite.js";
+
+runAgGridDataSuite({
+ pagePath: "/v33/index.html",
+ versionLabel: "v33",
+});
diff --git a/packages/playwright-ag-grid/tests/ag-grid-data.v34.spec.js b/packages/playwright-ag-grid/tests/ag-grid-data.v34.spec.js
new file mode 100644
index 0000000..4e8acf2
--- /dev/null
+++ b/packages/playwright-ag-grid/tests/ag-grid-data.v34.spec.js
@@ -0,0 +1,6 @@
+import { runAgGridDataSuite } from "./shared/run-ag-grid-data-suite.js";
+
+runAgGridDataSuite({
+ pagePath: "/v34/index.html",
+ versionLabel: "v34",
+});
diff --git a/packages/playwright-ag-grid/tests/ag-grid-data.v35.spec.js b/packages/playwright-ag-grid/tests/ag-grid-data.v35.spec.js
new file mode 100644
index 0000000..2020b05
--- /dev/null
+++ b/packages/playwright-ag-grid/tests/ag-grid-data.v35.spec.js
@@ -0,0 +1,6 @@
+import { runAgGridDataSuite } from "./shared/run-ag-grid-data-suite.js";
+
+runAgGridDataSuite({
+ pagePath: "/index.html",
+ versionLabel: "v35",
+});
diff --git a/packages/playwright-ag-grid/tests/ag-grid-elements.v33.spec.js b/packages/playwright-ag-grid/tests/ag-grid-elements.v33.spec.js
new file mode 100644
index 0000000..12a3e31
--- /dev/null
+++ b/packages/playwright-ag-grid/tests/ag-grid-elements.v33.spec.js
@@ -0,0 +1,6 @@
+import { runAgGridElementsSuite } from "./shared/run-ag-grid-elements-suite.js";
+
+runAgGridElementsSuite({
+ pagePath: "/v33/index.html",
+ versionLabel: "v33",
+});
diff --git a/packages/playwright-ag-grid/tests/ag-grid-elements.v34.spec.js b/packages/playwright-ag-grid/tests/ag-grid-elements.v34.spec.js
new file mode 100644
index 0000000..c188e23
--- /dev/null
+++ b/packages/playwright-ag-grid/tests/ag-grid-elements.v34.spec.js
@@ -0,0 +1,6 @@
+import { runAgGridElementsSuite } from "./shared/run-ag-grid-elements-suite.js";
+
+runAgGridElementsSuite({
+ pagePath: "/v34/index.html",
+ versionLabel: "v34",
+});
diff --git a/packages/playwright-ag-grid/tests/ag-grid-elements.v35.spec.js b/packages/playwright-ag-grid/tests/ag-grid-elements.v35.spec.js
new file mode 100644
index 0000000..3ac2c18
--- /dev/null
+++ b/packages/playwright-ag-grid/tests/ag-grid-elements.v35.spec.js
@@ -0,0 +1,6 @@
+import { runAgGridElementsSuite } from "./shared/run-ag-grid-elements-suite.js";
+
+runAgGridElementsSuite({
+ pagePath: "/index.html",
+ versionLabel: "v35",
+});
diff --git a/packages/playwright-ag-grid/tests/server.mjs b/packages/playwright-ag-grid/tests/server.mjs
new file mode 100644
index 0000000..a4905be
--- /dev/null
+++ b/packages/playwright-ag-grid/tests/server.mjs
@@ -0,0 +1,47 @@
+import http from "node:http";
+import fs from "node:fs";
+import path from "node:path";
+import { fileURLToPath } from "node:url";
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+const appRoot = path.resolve(__dirname, "../../cypress-ag-grid/app");
+const port = 4173;
+
+const contentTypes = {
+ ".css": "text/css; charset=utf-8",
+ ".html": "text/html; charset=utf-8",
+ ".js": "application/javascript; charset=utf-8",
+ ".json": "application/json; charset=utf-8",
+ ".png": "image/png",
+};
+
+http
+ .createServer((req, res) => {
+ const requestPath = req.url === "/" ? "/index.html" : req.url;
+ const safePath = path.normalize(decodeURIComponent(requestPath)).replace(/^(\.\.[/\\])+/, "");
+ const filePath = path.join(appRoot, safePath);
+
+ if (!filePath.startsWith(appRoot)) {
+ res.writeHead(403);
+ res.end("Forbidden");
+ return;
+ }
+
+ fs.readFile(filePath, (error, data) => {
+ if (error) {
+ res.writeHead(error.code === "ENOENT" ? 404 : 500);
+ res.end(error.code === "ENOENT" ? "Not found" : "Server error");
+ return;
+ }
+
+ res.writeHead(200, {
+ "Content-Type":
+ contentTypes[path.extname(filePath)] || "application/octet-stream",
+ });
+ res.end(data);
+ });
+ })
+ .listen(port, "127.0.0.1", () => {
+ console.log(`Playwright AG Grid test server listening on ${port}`);
+ });
diff --git a/packages/playwright-ag-grid/tests/shared/fixtures.js b/packages/playwright-ag-grid/tests/shared/fixtures.js
new file mode 100644
index 0000000..3e92b6e
--- /dev/null
+++ b/packages/playwright-ag-grid/tests/shared/fixtures.js
@@ -0,0 +1,109 @@
+import fs from "node:fs";
+import path from "node:path";
+import { fileURLToPath } from "node:url";
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+const cardataPath = path.resolve(
+ __dirname,
+ "../../../cypress-ag-grid/cypress/fixtures/cardata.json"
+);
+
+export const agGridSelector = "#myGrid";
+export const agGridElementsSelector = "#myGrid2";
+
+export const pageSize = 5;
+
+export const cardataFixture = JSON.parse(
+ fs.readFileSync(cardataPath, "utf-8")
+);
+
+export const expectedPaginatedTableData = [
+ [
+ { Year: "2020", Make: "Toyota", Model: "Celica", Condition: "fair", Price: "35000" },
+ { Year: "2020", Make: "Ford", Model: "Mondeo", Condition: "excellent", Price: "32000" },
+ { Year: "2020", Make: "Porsche", Model: "Boxter", Condition: "good", Price: "72000" },
+ { Year: "2020", Make: "BMW", Model: "3-series", Condition: "fair", Price: "45000" },
+ { Year: "2020", Make: "Mercedes", Model: "GLC300", Condition: "good", Price: "53000" },
+ ],
+ [
+ { Year: "2020", Make: "Honda", Model: "Civic", Condition: "poor", Price: "22000" },
+ { Year: "2020", Make: "Honda", Model: "Accord", Condition: "poor", Price: "32000" },
+ { Year: "2020", Make: "Ford", Model: "Taurus", Condition: "excellent", Price: "19000" },
+ { Year: "2020", Make: "Hyundai", Model: "Elantra", Condition: "good", Price: "22000" },
+ { Year: "2020", Make: "Toyota", Model: "Celica", Condition: "poor", Price: "5000" },
+ ],
+ [
+ { Year: "2020", Make: "Ford", Model: "Mondeo", Condition: "good", Price: "25000" },
+ { Year: "2020", Make: "Porsche", Model: "Boxter", Condition: "good", Price: "99000" },
+ { Year: "2020", Make: "BMW", Model: "3-series", Condition: "poor", Price: "32000" },
+ { Year: "2020", Make: "Mercedes", Model: "GLC300", Condition: "excellent", Price: "35000" },
+ { Year: "2011", Make: "Honda", Model: "Civic", Condition: "good", Price: "9000" },
+ ],
+ [
+ { Year: "2020", Make: "Honda", Model: "Accord", Condition: "good", Price: "34000" },
+ { Year: "1990", Make: "Ford", Model: "Taurus", Condition: "excellent", Price: "900" },
+ { Year: "2020", Make: "Hyundai", Model: "Elantra", Condition: "fair", Price: "3000" },
+ { Year: "2020", Make: "BMW", Model: "2002", Condition: "excellent", Price: "88001" },
+ { Year: "2023", Make: "Hyundai", Model: "Santa Fe", Condition: "excellent", Price: "" },
+ ],
+];
+
+export const expectedFirstPageTableData = expectedPaginatedTableData[0];
+
+export const expectedPorscheRowsBeforeEditing = [
+ { Year: "2020", Make: "Porsche", Model: "Boxter", Price: "72000" },
+ { Year: "2020", Make: "Porsche", Model: "Boxter", Price: "99000" },
+];
+
+export const expectedPorscheRowsAfterEditing = [
+ { Year: "2020", Make: "Porsche", Model: "Boxter", Price: "66000" },
+ { Year: "2020", Make: "Porsche", Model: "Boxter", Price: "99000" },
+];
+
+export function clone(value) {
+ return structuredClone(value);
+}
+
+export function removePropertyFromCollection(collection, columnsToExclude) {
+ const cloned = clone(collection);
+
+ if (!columnsToExclude) {
+ return cloned;
+ }
+
+ for (const excludedColumn of columnsToExclude) {
+ for (const row of cloned) {
+ delete row[excludedColumn];
+ }
+ }
+
+ return cloned;
+}
+
+export function sortedCollectionByProperty(collection, columnName, sortedBy, pageSizeLimit = pageSize) {
+ const cloned = clone(collection);
+ const direction = sortedBy === "desc" ? -1 : 1;
+
+ return cloned
+ .sort((a, b) => {
+ const valueA = String(a[columnName] ?? "").toUpperCase();
+ const valueB = String(b[columnName] ?? "").toUpperCase();
+ if (valueA < valueB) return -1 * direction;
+ if (valueA > valueB) return 1 * direction;
+ return 0;
+ })
+ .slice(0, pageSizeLimit);
+}
+
+export function getSortedMileage(actualTableData) {
+ return actualTableData
+ .map((row) => row.Mileage)
+ .sort((a, b) => Number(a) - Number(b));
+}
+
+export function expectRowsSubset(expect, actualRows, expectedRows) {
+ for (const expectedRow of expectedRows) {
+ expect(actualRows).toContainEqual(expectedRow);
+ }
+}
diff --git a/packages/playwright-ag-grid/tests/shared/run-ag-grid-data-suite.js b/packages/playwright-ag-grid/tests/shared/run-ag-grid-data-suite.js
new file mode 100644
index 0000000..018f63f
--- /dev/null
+++ b/packages/playwright-ag-grid/tests/shared/run-ag-grid-data-suite.js
@@ -0,0 +1,598 @@
+import { expect, test } from "@playwright/test";
+
+import { createAgGrid, filterOperator, sort } from "../../src/index.js";
+import {
+ agGridSelector,
+ cardataFixture,
+ clone,
+ expectRowsSubset,
+ expectedFirstPageTableData,
+ expectedPaginatedTableData,
+ getSortedMileage,
+ pageSize,
+ removePropertyFromCollection,
+ sortedCollectionByProperty,
+} from "./fixtures.js";
+
+async function enableMileageNumberFilter(page, floatingFilter = false) {
+ await page.evaluate(({ field, filter, floatingFilterValue, hide }) => {
+ window.setColumnFilter(field, filter, floatingFilterValue, hide);
+ }, {
+ field: "mileage",
+ filter: "agNumberColumnFilter",
+ floatingFilterValue: floatingFilter,
+ hide: false,
+ });
+ await page.locator(".ag-cell").first().waitFor({ state: "visible" });
+}
+
+async function validatePaginatedTable(page, grid, expectedPages, options) {
+ for (let index = 0; index < expectedPages.length; index += 1) {
+ await expect(await grid.getData(options)).toEqual(expectedPages[index]);
+ if (index < expectedPages.length - 1) {
+ await page.locator(`${agGridSelector} .ag-icon-next`).click();
+ }
+ }
+}
+
+function sortRowsByMileage(rows) {
+ return clone(rows).sort((a, b) => Number(a.Mileage) - Number(b.Mileage));
+}
+
+export function runAgGridDataSuite({ pagePath, versionLabel }) {
+ test.describe(`playwright ag-grid data scenarios (${versionLabel})`, () => {
+ test.beforeEach(async ({ page }) => {
+ await page.goto(pagePath);
+ await expect(page.locator(".example-version")).toContainText(`AG Grid ${versionLabel}`);
+ await page.locator(".ag-cell").first().waitFor({ state: "visible" });
+ await page.locator("#floating").click();
+ });
+
+ test("verify paginated table data - any order - include all columns", async ({ page }) => {
+ const grid = createAgGrid(page.locator(agGridSelector));
+ await validatePaginatedTable(page, grid, expectedPaginatedTableData);
+ });
+
+ test("verify paginated table data - exact order - include all columns", async ({ page }) => {
+ const grid = createAgGrid(page.locator(agGridSelector));
+ await expect(await grid.getData()).toEqual(expectedPaginatedTableData[0]);
+ });
+
+ test("verify exact order table data when columns are not in order - include all columns", async ({ page }) => {
+ const grid = createAgGrid(page.locator(agGridSelector));
+ await grid.pinColumn("Price", "left");
+ await expect(await grid.getData()).toEqual(expectedPaginatedTableData[0]);
+ });
+
+ test("verify paginated table data - excluding columns", async ({ page }) => {
+ const grid = createAgGrid(page.locator(agGridSelector));
+ const expectedSubset = expectedPaginatedTableData.map((pageRows) =>
+ pageRows.map(({ Year, Make, Model }) => ({ Year, Make, Model }))
+ );
+
+ await validatePaginatedTable(page, grid, expectedSubset, {
+ onlyColumns: ["Year", "Make", "Model"],
+ });
+ });
+
+ test("able to filter by checkbox", async ({ page }) => {
+ const grid = createAgGrid(page.locator(agGridSelector));
+ await grid.filterTextFloating({
+ searchCriteria: {
+ columnName: "Model",
+ filterValue: "2002",
+ },
+ selectAllLocaleText: "(Select All)",
+ hasApplyButton: true,
+ });
+ await expect(await grid.getData()).toEqual([
+ { Year: "2020", Make: "BMW", Model: "2002", Condition: "excellent", Price: "88001" },
+ ]);
+ });
+
+ test("able to filter by checkbox - multiple columns", async ({ page }) => {
+ const grid = createAgGrid(page.locator(agGridSelector));
+ await page.locator("#nonFloating").click();
+ await grid.filterCheckboxMenu({
+ searchCriteria: [
+ { columnName: "Model", filterValue: "2002" },
+ { columnName: "Model", filterValue: "3-series" },
+ ],
+ hasApplyButton: true,
+ });
+ await expect(await grid.getData()).toEqual([
+ { Year: "2020", Make: "BMW", Model: "3-series", Condition: "fair", Price: "45000" },
+ { Year: "2020", Make: "BMW", Model: "3-series", Condition: "poor", Price: "32000" },
+ { Year: "2020", Make: "BMW", Model: "2002", Condition: "excellent", Price: "88001" },
+ ]);
+ });
+
+ test("able to filter by text - menu", async ({ page }) => {
+ const grid = createAgGrid(page.locator(agGridSelector));
+ await grid.sortColumn("Model", sort.ascending);
+ await grid.filterTextMenu({
+ searchCriteria: {
+ columnName: "Price",
+ filterValue: "32000",
+ operator: filterOperator.equals,
+ },
+ hasApplyButton: true,
+ });
+ await expect(await grid.getData()).toEqual([
+ { Year: "2020", Make: "BMW", Model: "3-series", Condition: "poor", Price: "32000" },
+ { Year: "2020", Make: "Honda", Model: "Accord", Condition: "poor", Price: "32000" },
+ { Year: "2020", Make: "Ford", Model: "Mondeo", Condition: "excellent", Price: "32000" },
+ ]);
+ });
+
+ test("able to filter by text - menu - multiple columns", async ({ page }) => {
+ const grid = createAgGrid(page.locator(agGridSelector));
+ await page.locator("#nonFloating").click();
+ await grid.sortColumn("Model", sort.ascending);
+ await grid.filterTextMenu({
+ searchCriteria: [
+ {
+ columnName: "Price",
+ filterValue: "32000",
+ operator: filterOperator.equals,
+ },
+ {
+ columnName: "Make",
+ filterValue: "BMW",
+ operator: filterOperator.equals,
+ },
+ ],
+ hasApplyButton: true,
+ });
+ await expect(await grid.getData()).toEqual([
+ { Year: "2020", Make: "BMW", Model: "3-series", Condition: "poor", Price: "32000" },
+ ]);
+ });
+
+ test("able to filter by text - menu - contains operator", async ({ page }) => {
+ const grid = createAgGrid(page.locator(agGridSelector));
+ await grid.sortColumn("Model", sort.ascending);
+ await grid.filterTextFloating({
+ searchCriteria: {
+ columnName: "Make",
+ filterValue: "ord",
+ operator: filterOperator.contains,
+ },
+ hasApplyButton: true,
+ });
+ await expect(await grid.getData()).toEqual([
+ { Year: "2020", Make: "Ford", Model: "Mondeo", Condition: "excellent", Price: "32000" },
+ { Year: "2020", Make: "Ford", Model: "Mondeo", Condition: "good", Price: "25000" },
+ { Year: "2020", Make: "Ford", Model: "Taurus", Condition: "excellent", Price: "19000" },
+ { Year: "1990", Make: "Ford", Model: "Taurus", Condition: "excellent", Price: "900" },
+ ]);
+ });
+
+ test("able to filter by text - menu - does not contain operator", async ({ page }) => {
+ const grid = createAgGrid(page.locator(agGridSelector));
+ await grid.filterTextFloating({
+ searchCriteria: {
+ columnName: "Make",
+ filterValue: "ord",
+ operator: filterOperator.notContains,
+ },
+ hasApplyButton: true,
+ });
+
+ const actualTableData = await grid.getData();
+ expect(actualTableData.length).toBeGreaterThan(0);
+ for (const row of actualTableData) {
+ expect(row.Make).not.toContain("ord");
+ }
+ });
+
+ test("able to filter by text - menu - does not equal operator", async ({ page }) => {
+ const grid = createAgGrid(page.locator(agGridSelector));
+ await grid.filterTextFloating({
+ searchCriteria: {
+ columnName: "Make",
+ filterValue: "Ford",
+ operator: filterOperator.notEquals,
+ },
+ hasApplyButton: true,
+ });
+
+ const actualTableData = await grid.getData();
+ expect(actualTableData.length).toBeGreaterThan(0);
+ for (const row of actualTableData) {
+ expect(row.Make).not.toBe("Ford");
+ }
+ });
+
+ test("able to filter by text - menu - less than operator", async ({ page }) => {
+ const grid = createAgGrid(page.locator(agGridSelector));
+ await enableMileageNumberFilter(page);
+ await grid.filterTextMenu({
+ searchCriteria: {
+ columnName: "Mileage",
+ filterValue: "5000",
+ operator: filterOperator.lessThan,
+ },
+ hasApplyButton: true,
+ });
+ expect(getSortedMileage(await grid.getData())).toEqual(["250", "1000", "3500", "4500"]);
+ });
+
+ test("able to filter by text - menu - less than or equal operator", async ({ page }) => {
+ const grid = createAgGrid(page.locator(agGridSelector));
+ await enableMileageNumberFilter(page);
+ await grid.filterTextMenu({
+ searchCriteria: {
+ columnName: "Mileage",
+ filterValue: "5000",
+ operator: filterOperator.lessThanOrEquals,
+ },
+ hasApplyButton: true,
+ });
+ expect(getSortedMileage(await grid.getData())).toEqual(["250", "1000", "3500", "4500", "5000"]);
+ });
+
+ test("able to filter by text - menu - greater than operator", async ({ page }) => {
+ const grid = createAgGrid(page.locator(agGridSelector));
+ await enableMileageNumberFilter(page);
+ await grid.filterTextMenu({
+ searchCriteria: {
+ columnName: "Mileage",
+ filterValue: "50000",
+ operator: filterOperator.greaterThan,
+ },
+ hasApplyButton: true,
+ });
+ expect(getSortedMileage(await grid.getData())).toEqual(["52000", "60000", "70000", "90000"]);
+ });
+
+ test("able to filter by text - menu - greater than or equal operator", async ({ page }) => {
+ const grid = createAgGrid(page.locator(agGridSelector));
+ await enableMileageNumberFilter(page);
+ await grid.filterTextMenu({
+ searchCriteria: {
+ columnName: "Mileage",
+ filterValue: "50000",
+ operator: filterOperator.greaterThanOrEquals,
+ },
+ hasApplyButton: true,
+ });
+ expect(getSortedMileage(await grid.getData())).toEqual(["52000", "60000", "70000", "90000"]);
+ });
+
+ test("able to filter by text - floating filter", async ({ page }) => {
+ const grid = createAgGrid(page.locator(agGridSelector));
+ await grid.sortColumn("Model", sort.ascending);
+ await grid.filterTextFloating({
+ searchCriteria: {
+ columnName: "Make",
+ filterValue: "Ford",
+ },
+ hasApplyButton: true,
+ });
+ await expect(await grid.getData()).toEqual([
+ { Year: "2020", Make: "Ford", Model: "Mondeo", Condition: "excellent", Price: "32000" },
+ { Year: "2020", Make: "Ford", Model: "Mondeo", Condition: "good", Price: "25000" },
+ { Year: "2020", Make: "Ford", Model: "Taurus", Condition: "excellent", Price: "19000" },
+ { Year: "1990", Make: "Ford", Model: "Taurus", Condition: "excellent", Price: "900" },
+ ]);
+ });
+
+ test("able to filter by text - floating filter - multiple conditions", async ({ page }) => {
+ const grid = createAgGrid(page.locator(agGridSelector));
+ await grid.sortColumn("Model", sort.ascending);
+ await grid.filterTextFloating({
+ searchCriteria: {
+ columnName: "Make",
+ filterValue: "B",
+ searchInputIndex: 0,
+ },
+ hasApplyButton: true,
+ });
+ await grid.filterTextFloating({
+ searchCriteria: {
+ columnName: "Make",
+ filterValue: "MW",
+ searchInputIndex: 1,
+ },
+ hasApplyButton: true,
+ });
+ await expect(await grid.getData()).toEqual([
+ { Year: "2020", Make: "BMW", Model: "2002", Condition: "excellent", Price: "88001" },
+ { Year: "2020", Make: "BMW", Model: "3-series", Condition: "fair", Price: "45000" },
+ { Year: "2020", Make: "BMW", Model: "3-series", Condition: "poor", Price: "32000" },
+ ]);
+ });
+
+ test("able to filter by text - floating filter - multiple columns", async ({ page }) => {
+ const grid = createAgGrid(page.locator(agGridSelector));
+ await grid.sortColumn("Model", sort.ascending);
+ await grid.filterTextFloating({
+ searchCriteria: [
+ { columnName: "Make", filterValue: "Ford" },
+ { columnName: "Year", filterValue: "1990" },
+ ],
+ hasApplyButton: true,
+ });
+ await expect(await grid.getData()).toEqual([
+ { Year: "1990", Make: "Ford", Model: "Taurus", Condition: "excellent", Price: "900" },
+ ]);
+ });
+
+ test("able to filter by text - floating filter - between operator", async ({ page }) => {
+ const grid = createAgGrid(page.locator(agGridSelector));
+ await enableMileageNumberFilter(page, true);
+ await grid.filterTextFloating({
+ searchCriteria: [
+ {
+ columnName: "Mileage",
+ filterValue: "0",
+ operator: filterOperator.inRange,
+ },
+ {
+ columnName: "Mileage",
+ filterValue: "5000",
+ operator: filterOperator.inRange,
+ },
+ ],
+ hasApplyButton: true,
+ });
+
+ const expectedTableData = [
+ { Year: "2023", Make: "Hyundai", Model: "Santa Fe", Condition: "excellent", Mileage: "250", Price: "" },
+ { Year: "2020", Make: "Porsche", Model: "Boxter", Condition: "good", Mileage: "1000", Price: "99000" },
+ { Year: "2020", Make: "Hyundai", Model: "Elantra", Condition: "fair", Mileage: "3500", Price: "3000" },
+ { Year: "2020", Make: "BMW", Model: "2002", Condition: "excellent", Mileage: "4500", Price: "88001" },
+ ];
+
+ expect(sortRowsByMileage(await grid.getData())).toEqual(sortRowsByMileage(expectedTableData));
+ });
+
+ test("able to filter by text - floating filter - between operator with explicit indexes", async ({ page }) => {
+ const grid = createAgGrid(page.locator(agGridSelector));
+ await enableMileageNumberFilter(page, true);
+
+ if (versionLabel === "v33") {
+ await grid.filterTextFloating({
+ searchCriteria: [
+ {
+ columnName: "Mileage",
+ filterValue: "0",
+ operator: filterOperator.inRange,
+ searchInputIndex: 0,
+ operatorIndex: 0,
+ },
+ {
+ columnName: "Mileage",
+ filterValue: "5000",
+ operator: filterOperator.inRange,
+ searchInputIndex: 1,
+ operatorIndex: 0,
+ },
+ ],
+ hasApplyButton: true,
+ });
+ } else {
+ await grid.filterTextFloating({
+ searchCriteria: {
+ columnName: "Mileage",
+ filterValue: "0",
+ operator: filterOperator.inRange,
+ searchInputIndex: 0,
+ operatorIndex: 0,
+ },
+ hasApplyButton: true,
+ });
+ await grid.filterTextFloating({
+ searchCriteria: {
+ columnName: "Mileage",
+ filterValue: "5000",
+ operator: filterOperator.inRange,
+ searchInputIndex: 1,
+ operatorIndex: 0,
+ },
+ hasApplyButton: true,
+ });
+ }
+
+ expect(getSortedMileage(await grid.getData())).toEqual(["250", "1000", "3500", "4500"]);
+ });
+
+ test("able to filter by text - floating filter - between operator with mixed criteria", async ({ page }) => {
+ const grid = createAgGrid(page.locator(agGridSelector));
+ await enableMileageNumberFilter(page, true);
+ await grid.filterTextFloating({
+ searchCriteria: [
+ {
+ columnName: "Mileage",
+ filterValue: "0",
+ operator: filterOperator.inRange,
+ },
+ {
+ columnName: "Mileage",
+ filterValue: "500",
+ operator: filterOperator.inRange,
+ },
+ {
+ columnName: "Make",
+ filterValue: "Ford",
+ },
+ ],
+ hasApplyButton: true,
+ });
+ await expect(await grid.getData()).toEqual([]);
+ });
+
+ test("able to filter by text - floating filter - between operator without apply button", async ({ page }) => {
+ const grid = createAgGrid(page.locator(agGridSelector));
+ await enableMileageNumberFilter(page, true);
+ await grid.filterTextFloating({
+ searchCriteria: [
+ {
+ columnName: "Mileage",
+ filterValue: "0",
+ operator: filterOperator.inRange,
+ },
+ {
+ columnName: "Mileage",
+ filterValue: "5000",
+ operator: filterOperator.inRange,
+ },
+ ],
+ hasApplyButton: false,
+ noMenuTabs: true,
+ });
+ expect(getSortedMileage(await grid.getData())).toEqual(["250", "1000", "3500", "4500"]);
+ });
+
+ test("able to filter by text - floating filter - multi filter", async ({ page }) => {
+ const grid = createAgGrid(page.locator(agGridSelector));
+ await grid.sortColumn("Model", sort.ascending);
+ await grid.filterTextFloating({
+ searchCriteria: [
+ {
+ columnName: "Model",
+ filterValue: "Taurus",
+ isMultiFilter: true,
+ },
+ ],
+ hasApplyButton: true,
+ });
+ await expect(await grid.getData()).toEqual([
+ { Year: "2020", Make: "Ford", Model: "Taurus", Condition: "excellent", Price: "19000" },
+ { Year: "1990", Make: "Ford", Model: "Taurus", Condition: "excellent", Price: "900" },
+ ]);
+ });
+
+ test("able to validate empty table", async ({ page }) => {
+ const grid = createAgGrid(page.locator(agGridSelector));
+ await grid.filterTextMenu({
+ searchCriteria: {
+ columnName: "Price",
+ filterValue: "0",
+ operator: filterOperator.equals,
+ },
+ hasApplyButton: true,
+ });
+ await expect(await grid.getData()).toEqual([]);
+ });
+
+ test("able to sort by ascending order", async ({ page }) => {
+ const grid = createAgGrid(page.locator(agGridSelector));
+ await grid.sortColumn("Make", sort.ascending);
+ const expectedDataSortedByAscending = sortedCollectionByProperty(
+ removePropertyFromCollection(cardataFixture, ["Mileage"]),
+ "Make",
+ sort.ascending,
+ pageSize
+ );
+ await expect(await grid.getData()).toEqual(expectedDataSortedByAscending);
+ });
+
+ test("able to sort by descending order", async ({ page }) => {
+ const grid = createAgGrid(page.locator(agGridSelector));
+ await grid.sortColumn("Make", sort.descending);
+ const expectedDataSortedByDescending = sortedCollectionByProperty(
+ removePropertyFromCollection(cardataFixture, ["Mileage"]),
+ "Make",
+ sort.descending,
+ pageSize
+ );
+ await expect(await grid.getData()).toEqual(expectedDataSortedByDescending);
+ });
+
+ test("remove column from grid and verify select column data", async ({ page }) => {
+ const grid = createAgGrid(page.locator(agGridSelector));
+ await grid.toggleColumnFromSideBar("Year", true);
+ const expectedData = removePropertyFromCollection(
+ removePropertyFromCollection(cardataFixture, ["Mileage"]),
+ ["Year"]
+ ).slice(0, pageSize);
+ await expect(await grid.getData()).toEqual(expectedData);
+ });
+
+ test("remove single pinned column from grid and verify select column data", async ({ page }) => {
+ const grid = createAgGrid(page.locator(agGridSelector));
+ await grid.toggleColumnFromSideBar("Price", true);
+ const expectedData = removePropertyFromCollection(
+ removePropertyFromCollection(cardataFixture, ["Mileage"]),
+ ["Price"]
+ ).slice(0, pageSize);
+ await expect(await grid.getData()).toEqual(expectedData);
+ });
+
+ test("remove multiple columns from grid and verify select column data", async ({ page }) => {
+ const grid = createAgGrid(page.locator(agGridSelector));
+ await grid.toggleColumnFromSideBar("Price", true);
+ await grid.toggleColumnFromSideBar("Make", true);
+ const expectedData = removePropertyFromCollection(
+ removePropertyFromCollection(cardataFixture, ["Mileage"]),
+ ["Price", "Make"]
+ ).slice(0, pageSize);
+ await expect(await grid.getData()).toEqual(expectedData);
+ });
+
+ test("only validate select column data", async ({ page }) => {
+ const grid = createAgGrid(page.locator(agGridSelector));
+ const actualTableData = await grid.getData({ onlyColumns: ["Year", "Make", "Model"] });
+ expectRowsSubset(expect, actualTableData, expectedFirstPageTableData.map(({ Year, Make, Model }) => ({ Year, Make, Model })));
+ });
+
+ test("able to filter by 'Blank'", async ({ page }) => {
+ const grid = createAgGrid(page.locator(agGridSelector));
+ await grid.filterTextMenu({
+ searchCriteria: {
+ columnName: "Price",
+ operator: filterOperator.blank,
+ },
+ hasApplyButton: true,
+ });
+ expectRowsSubset(expect, await grid.getData(), [
+ { Year: "2023", Make: "Hyundai", Model: "Santa Fe", Condition: "excellent", Price: "" },
+ ]);
+ });
+
+ test("able to filter by 'Not blank'", async ({ page }) => {
+ const grid = createAgGrid(page.locator(agGridSelector));
+ await grid.filterTextMenu({
+ searchCriteria: {
+ columnName: "Price",
+ operator: filterOperator.notBlank,
+ },
+ hasApplyButton: true,
+ });
+ const actualTableData = await grid.getData();
+ expect(actualTableData.length).toBeGreaterThan(0);
+ for (const row of actualTableData) {
+ expect(row.Price).not.toBe("");
+ }
+ });
+
+ test("able to filter by agTextColumnFilter with join operator", async ({ page }) => {
+ const grid = createAgGrid(page.locator(agGridSelector));
+ await grid.filterTextFloating({
+ searchCriteria: {
+ columnName: "Condition",
+ operator: filterOperator.startsWith,
+ filterValue: "f",
+ searchInputIndex: 0,
+ },
+ hasApplyButton: true,
+ });
+ await grid.filterTextFloating({
+ searchCriteria: {
+ columnName: "Condition",
+ operator: filterOperator.endsWith,
+ filterValue: "ir",
+ searchInputIndex: 1,
+ },
+ hasApplyButton: true,
+ });
+ expectRowsSubset(expect, await grid.getData(), [
+ { Year: "2020", Make: "Toyota", Model: "Celica", Condition: "fair", Price: "35000" },
+ { Year: "2020", Make: "BMW", Model: "3-series", Condition: "fair", Price: "45000" },
+ { Year: "2020", Make: "Hyundai", Model: "Elantra", Condition: "fair", Price: "3000" },
+ ]);
+ });
+ });
+}
diff --git a/packages/playwright-ag-grid/tests/shared/run-ag-grid-elements-suite.js b/packages/playwright-ag-grid/tests/shared/run-ag-grid-elements-suite.js
new file mode 100644
index 0000000..d30ae5f
--- /dev/null
+++ b/packages/playwright-ag-grid/tests/shared/run-ag-grid-elements-suite.js
@@ -0,0 +1,49 @@
+import { expect, test } from "@playwright/test";
+
+import { createAgGrid, filterOperator } from "../../src/index.js";
+import {
+ agGridElementsSelector,
+ expectedPorscheRowsAfterEditing,
+ expectedPorscheRowsBeforeEditing,
+} from "./fixtures.js";
+
+function expectRowsSubset(actualRows, expectedRows) {
+ for (const expectedRow of expectedRows) {
+ expect(actualRows).toContainEqual(expectedRow);
+ }
+}
+
+export function runAgGridElementsSuite({ pagePath, versionLabel }) {
+ test.describe(`playwright ag-grid elements scenarios (${versionLabel})`, () => {
+ test.beforeEach(async ({ page }) => {
+ await page.goto(pagePath);
+ await expect(page.locator(".example-version")).toContainText(`AG Grid ${versionLabel}`);
+ await page.locator(".ag-cell").first().waitFor({ state: "visible" });
+ });
+
+ test("updates a grid cell value", async ({ page }) => {
+ const grid = createAgGrid(page.locator(agGridElementsSelector));
+
+ await grid.filterTextFloating({
+ searchCriteria: {
+ columnName: "Make",
+ filterValue: "Porsche",
+ operator: filterOperator.equals,
+ },
+ hasApplyButton: true,
+ });
+
+ expectRowsSubset(await grid.getData(), expectedPorscheRowsBeforeEditing);
+
+ const priceCell = await grid.getCellLocator(
+ { Make: "Porsche", Price: "72000" },
+ "Price"
+ );
+ await priceCell.dblclick();
+ await priceCell.locator("input").fill("66000");
+ await priceCell.locator("input").press("Enter");
+
+ expectRowsSubset(await grid.getData(), expectedPorscheRowsAfterEditing);
+ });
+ });
+}
diff --git a/test.ts b/test.ts
new file mode 100644
index 0000000..fee5994
--- /dev/null
+++ b/test.ts
@@ -0,0 +1,60 @@
+type TestResult = {
+ testName: string;
+ suite: string;
+ status: "passed" | "failed" | "skipped";
+ durationMs: number;
+ retries: number;
+ errorMessage?: string;
+}
+
+const results: TestResult[] = [
+ { testName: "login valid user", suite: "auth", status: "passed", durationMs: 1200, retries: 0 },
+ { testName: "login invalid password", suite: "auth", status: "failed", durationMs: 900, retries: 0, errorMessage: "401 !== 200" },
+ { testName: "reset password", suite: "auth", status: "passed", durationMs: 1500, retries: 1 },
+ { testName: "create order", suite: "orders", status: "passed", durationMs: 2200, retries: 0 },
+ { testName: "cancel order", suite: "orders", status: "skipped", durationMs: 0, retries: 0 },
+ { testName: "refund order", suite: "orders", status: "failed", durationMs: 1800, retries: 2, errorMessage: "500 !== 200" },
+ { testName: "reset password", suite: "auth", status: "passed", durationMs: 1400, retries: 2 }
+];
+
+type Result = {
+ total: number;
+ passed: number;
+ failed: number;
+ skipped: number;
+ passRate: number; // percentage from 0 to 100, rounded to 2 decimals
+ flakyTests: string[]; // unique test names where retries > 0 and final status is passed
+ slowestTest?: { testName: string; durationMs: number };
+ failuresBySuite: Record;
+}
+
+function summarizeResults(results: TestResult[]): Result {
+ const passed = results.filter((r)=>r.status === "passed").length;
+ const total = results.length;
+ const failed = results.filter((r)=>r.status === "failed").length;
+ const skipped = results.filter((r)=>r.status === "skipped").length;
+ const slowestTest = results.sort((a,b) => a.durationMs - b.durationMs)[0];
+ const failuresBySuite: Record = {};
+
+ for (const result of results) {
+ if(result.status === "failed"){
+ if(!failuresBySuite){
+
+ }
+ }
+ }
+ console.log("FAILING SUITES:", failingSuites)
+
+ return{
+ flakyTests: results.filter((r) => r.retries >0 && r.status === "passed").map((r)=>r.testName),
+ passed,
+ total,
+ failed,
+ skipped,
+ passRate: passed / (passed + failed) * 100,
+ slowestTest: slowestTest ? { ...slowestTest} : undefined,
+ failuresBySuite: undefined
+ }
+}
+
+summarizeResults(results);