diff --git a/.github/actions/core-cicd/api-limits-check/Readme.md b/.github/actions/core-cicd/api-limits-check/Readme.md
new file mode 100644
index 000000000000..e5c7e8823e2e
--- /dev/null
+++ b/.github/actions/core-cicd/api-limits-check/Readme.md
@@ -0,0 +1,32 @@
+# Check GitHub API Rate Limit Action
+
+This GitHub Action allows you to check the current API rate limits for your GitHub account by querying the `/rate_limit` endpoint of the GitHub API. The action outputs the full JSON response, including rate limits for core, search, GraphQL, and other GitHub API resources.
+
+## Inputs
+
+| Input | Description | Required | Default |
+|--------|-------------|----------|---------|
+| `token` | The GitHub token to authenticate the API request. | true | `${{ github.token }}` |
+
+## Outputs
+
+The action outputs the full JSON response from the `/rate_limit` endpoint directly to the workflow log.
+
+## Usage
+
+Here’s an example of how to use this action in a GitHub workflow:
+
+```yaml
+name: Check GitHub API Rate Limits
+
+on:
+ workflow_dispatch:
+
+jobs:
+ check-rate-limits:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Check API Rate Limit
+ uses: your-repo/check-rate-limit-action@v1
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
\ No newline at end of file
diff --git a/.github/actions/core-cicd/api-limits-check/action.yml b/.github/actions/core-cicd/api-limits-check/action.yml
new file mode 100644
index 000000000000..fbbe8e671f42
--- /dev/null
+++ b/.github/actions/core-cicd/api-limits-check/action.yml
@@ -0,0 +1,15 @@
+name: 'Check GitHub API Rate Limit'
+description: 'Outputs information on GitHub API /rate_limit endpoint'
+
+inputs:
+ token:
+ description: 'GitHub token to authenticate the API request'
+ required: true
+ default: ${{ github.token }}
+
+runs:
+ using: "composite"
+ steps:
+ - run: |
+ curl -s -H "Authorization: token ${{ inputs.token }}" https://api.github.com/rate_limit
+ shell: bash
diff --git a/.github/workflows/cicd_comp_finalize-phase.yml b/.github/workflows/cicd_comp_finalize-phase.yml
index 292519e1583a..2ce0b9b5a24a 100644
--- a/.github/workflows/cicd_comp_finalize-phase.yml
+++ b/.github/workflows/cicd_comp_finalize-phase.yml
@@ -183,4 +183,12 @@ jobs:
if [ "${{ needs.prepare-report-data.outputs.aggregate_status }}" != "SUCCESS" ]; then
echo "One or more jobs failed or cancelled!"
exit 1
- fi
\ No newline at end of file
+ fi
+
+ # Check can be removed if we have resolved root cause
+ # We cannot use a local github action for this as it is run before we checkout the repo
+ # secrets.GITHUB_TOKEN is not available in composite workflows so it needs to be passed in.
+ - name: Check API Rate Limit
+ shell: bash
+ run: |
+ curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN}}" https://api.github.com/rate_limit || true
\ No newline at end of file
diff --git a/.github/workflows/cicd_comp_initialize-phase.yml b/.github/workflows/cicd_comp_initialize-phase.yml
index 57846f6ca2d0..4a5b0163275f 100644
--- a/.github/workflows/cicd_comp_initialize-phase.yml
+++ b/.github/workflows/cicd_comp_initialize-phase.yml
@@ -64,6 +64,13 @@ jobs:
shell: bash
run: |
echo "Initializing..."
+ # Check can be removed if we have resolved root cause
+ # We cannot use a local github action for this as it is run before we checkout the repo
+ # secrets.GITHUB_TOKEN is not available in composite workflows so it needs to be passed in.
+ - name: Check API Rate Limit
+ shell: bash
+ run: |
+ curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" https://api.github.com/rate_limit || true
# This job checks for artifacts from previous builds and determines if they can be reused
check-previous-build:
diff --git a/.github/workflows/cicd_comp_pr-notifier.yml b/.github/workflows/cicd_comp_pr-notifier.yml
index 5188eff9ed69..29c8da8709f9 100644
--- a/.github/workflows/cicd_comp_pr-notifier.yml
+++ b/.github/workflows/cicd_comp_pr-notifier.yml
@@ -89,3 +89,10 @@ jobs:
-d "{ \"channel\":\"${channel}\",\"text\":\"${message}\"}" \
-s \
https://slack.com/api/chat.postMessage
+ # Check can be removed if we have resolved root cause
+ # We cannot use a local github action for this as it is run before we checkout the repo
+ # secrets.GITHUB_TOKEN is not available in composite workflows so it needs to be passed in.
+ - name: Check API Rate Limit
+ shell: bash
+ run: |
+ curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" https://api.github.com/rate_limit || true
\ No newline at end of file
diff --git a/.github/workflows/cicd_post-workflow-reporting.yml b/.github/workflows/cicd_post-workflow-reporting.yml
index c8bb5622c599..d8e8c71340af 100644
--- a/.github/workflows/cicd_post-workflow-reporting.yml
+++ b/.github/workflows/cicd_post-workflow-reporting.yml
@@ -271,4 +271,11 @@ jobs:
channel-id: ${{ vars.SLACK_REPORT_CHANNEL }}
payload: ${{ steps.prepare-slack-message.outputs.payload }}
json: true
- slack-bot-token: ${{ secrets.SLACK_BOT_TOKEN }}
+ slack-bot-token: ${{ secrets.SLACK_BOT_TOKEN }}
+ # Check can be removed if we have resolved root cause
+ # We cannot use a local github action for this as it is run before we checkout the repo
+ # secrets.GITHUB_TOKEN is not available in composite workflows so it needs to be passed in.
+ - name: Check API Rate Limit
+ shell: bash
+ run: |
+ curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" https://api.github.com/rate_limit || true
\ No newline at end of file
diff --git a/e2e/dotcms-e2e-node/frontend/.env b/e2e/dotcms-e2e-node/frontend/.env
new file mode 100644
index 000000000000..ad03f2f58dc1
--- /dev/null
+++ b/e2e/dotcms-e2e-node/frontend/.env
@@ -0,0 +1,2 @@
+CI=false
+BASE_URL=http://localhost:8080
diff --git a/e2e/dotcms-e2e-node/frontend/.env.ci b/e2e/dotcms-e2e-node/frontend/.env.ci
new file mode 100644
index 000000000000..8711f95abe93
--- /dev/null
+++ b/e2e/dotcms-e2e-node/frontend/.env.ci
@@ -0,0 +1 @@
+CI=true
diff --git a/e2e/dotcms-e2e-node/frontend/.env.local b/e2e/dotcms-e2e-node/frontend/.env.local
new file mode 100644
index 000000000000..f6c5e51966eb
--- /dev/null
+++ b/e2e/dotcms-e2e-node/frontend/.env.local
@@ -0,0 +1 @@
+BASE_URL=http://localhost:4200
\ No newline at end of file
diff --git a/e2e/dotcms-e2e-node/frontend/.gitignore b/e2e/dotcms-e2e-node/frontend/.gitignore
new file mode 100644
index 000000000000..68c5d18f00dc
--- /dev/null
+++ b/e2e/dotcms-e2e-node/frontend/.gitignore
@@ -0,0 +1,5 @@
+node_modules/
+/test-results/
+/playwright-report/
+/blob-report/
+/playwright/.cache/
diff --git a/e2e/dotcms-e2e-node/frontend/package.json b/e2e/dotcms-e2e-node/frontend/package.json
new file mode 100644
index 000000000000..854de4b4a9cd
--- /dev/null
+++ b/e2e/dotcms-e2e-node/frontend/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "frontend",
+ "version": "1.0.0",
+ "main": "index.js",
+ "license": "MIT",
+ "devDependencies": {
+ "@playwright/test": "^1.47.0",
+ "@types/node": "^22.5.4",
+ "dotenv": "^16.4.5"
+ },
+ "scripts": {
+ "start-local": "CURRENT_ENV=local yarn playwright test",
+ "start-ci": "CURRENT_ENV=ci yarn playwright test"
+ }
+}
diff --git a/e2e/dotcms-e2e-node/frontend/playwright.config.ts b/e2e/dotcms-e2e-node/frontend/playwright.config.ts
new file mode 100644
index 000000000000..6779a7d8d994
--- /dev/null
+++ b/e2e/dotcms-e2e-node/frontend/playwright.config.ts
@@ -0,0 +1,68 @@
+import dotenv from 'dotenv';
+import * as path from "node:path";
+import { defineConfig, devices } from '@playwright/test';
+
+const resolveEnvs = () => {
+ const envFiles = ['.env'];
+
+ if (process.env.CURRENT_ENV === 'local') {
+ envFiles.push('.env.local');
+ } else if (process.env.CURRENT_ENV === 'ci') {
+ envFiles.push('.env.ci');
+ }
+
+ envFiles.forEach((file) => {
+ dotenv.config({
+ path: path.resolve(__dirname, file),
+ override: true
+ });
+ });
+};
+
+resolveEnvs();
+
+/**
+ * See https://playwright.dev/docs/test-configuration.
+ */
+export default defineConfig({
+ testDir: './tests',
+ /* Run tests in files in parallel */
+ fullyParallel: true,
+ /* Fail the build on CI if you accidentally left test.only in the source code. */
+ forbidOnly: !!process.env.CI,
+ /* Retry on CI only */
+ retries: process.env.CI ? 2 : 0,
+ /* Opt out of parallel tests on CI. */
+ workers: process.env.CI ? 1 : undefined,
+ /* Reporter to use. See https://playwright.dev/docs/test-reporters */
+ reporter: 'html',
+ /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
+ use: {
+ baseURL: process.env.BASE_URL,
+ /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
+ trace: 'on-first-retry',
+ },
+ /* Configure projects for major browsers */
+ projects: [
+ {
+ name: 'chromium',
+ use: { ...devices['Desktop Chrome'] },
+ },
+
+ {
+ name: 'firefox',
+ use: { ...devices['Desktop Firefox'] },
+ },
+
+ {
+ name: 'webkit',
+ use: { ...devices['Desktop Safari'] },
+ },
+ ],
+ webServer: {
+ command: 'nx serve dotcms-ui',
+ cwd: '../../../core-web',
+ url: process.env.BASE_URL + '/dotAdmin',
+ reuseExistingServer: !!process.env.CI,
+ }
+});
diff --git a/e2e/dotcms-e2e-node/frontend/tests/welcome.spec.ts b/e2e/dotcms-e2e-node/frontend/tests/welcome.spec.ts
new file mode 100644
index 000000000000..a29905b6737d
--- /dev/null
+++ b/e2e/dotcms-e2e-node/frontend/tests/welcome.spec.ts
@@ -0,0 +1,8 @@
+import { test, expect } from '@playwright/test';
+
+test('has title', async ({ page }) => {
+ await page.goto('/dotAdmin');
+
+ // Expect h3 to contain a substring.
+ expect(await page.locator('h3').textContent()).toContain('Welcome!');
+});
diff --git a/e2e/dotcms-e2e-node/frontend/yarn.lock b/e2e/dotcms-e2e-node/frontend/yarn.lock
new file mode 100644
index 000000000000..2472d57e4e3b
--- /dev/null
+++ b/e2e/dotcms-e2e-node/frontend/yarn.lock
@@ -0,0 +1,46 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@playwright/test@^1.47.0":
+ version "1.47.0"
+ resolved "https://registry.npmjs.org/@playwright/test/-/test-1.47.0.tgz#69fc55b10754147cc20021afbfa05747d4961bf0"
+ integrity sha512-SgAdlSwYVpToI4e/IH19IHHWvoijAYH5hu2MWSXptRypLSnzj51PcGD+rsOXFayde4P9ZLi+loXVwArg6IUkCA==
+ dependencies:
+ playwright "1.47.0"
+
+"@types/node@^22.5.4":
+ version "22.5.4"
+ resolved "https://registry.npmjs.org/@types/node/-/node-22.5.4.tgz#83f7d1f65bc2ed223bdbf57c7884f1d5a4fa84e8"
+ integrity sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==
+ dependencies:
+ undici-types "~6.19.2"
+
+dotenv@^16.4.5:
+ version "16.4.5"
+ resolved "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f"
+ integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==
+
+fsevents@2.3.2:
+ version "2.3.2"
+ resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
+ integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
+
+playwright-core@1.47.0:
+ version "1.47.0"
+ resolved "https://registry.npmjs.org/playwright-core/-/playwright-core-1.47.0.tgz#b54ec060fd83e5c2e46b63986b5ebb5e96ace427"
+ integrity sha512-1DyHT8OqkcfCkYUD9zzUTfg7EfTd+6a8MkD/NWOvjo0u/SCNd5YmY/lJwFvUZOxJbWNds+ei7ic2+R/cRz/PDg==
+
+playwright@1.47.0:
+ version "1.47.0"
+ resolved "https://registry.npmjs.org/playwright/-/playwright-1.47.0.tgz#fb9b028883fad11362f9ff63ce7ba44bda0bf626"
+ integrity sha512-jOWiRq2pdNAX/mwLiwFYnPHpEZ4rM+fRSQpRHwEwZlP2PUANvL3+aJOF/bvISMhFD30rqMxUB4RJx9aQbfh4Ww==
+ dependencies:
+ playwright-core "1.47.0"
+ optionalDependencies:
+ fsevents "2.3.2"
+
+undici-types@~6.19.2:
+ version "6.19.8"
+ resolved "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02"
+ integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==
diff --git a/e2e/dotcms-e2e-node/pom.xml b/e2e/dotcms-e2e-node/pom.xml
new file mode 100644
index 000000000000..eb6903f1bdea
--- /dev/null
+++ b/e2e/dotcms-e2e-node/pom.xml
@@ -0,0 +1,182 @@
+
+
+ 4.0.0
+
+
+ com.dotcms
+ dotcms-nodejs-parent
+ ${revision}${sha1}${changelist}
+ ../../nodejs-parent/pom.xml
+
+
+ jar
+ dotcms-e2e-node
+
+
+ UTF-8
+ false
+ install --frozen-lockfile
+ global add playwright
+ true
+ local
+ run start-${e2e.test.env}
+ 8080
+ http://localhost:${tomcat.port}
+ ${ext.wiremock.api.key}
+ ${ext.docker.image.wiremock}
+ ./src/test/resources
+ /home/wiremock
+
+
+
+
+
+ com.github.eirslett
+ frontend-maven-plugin
+
+
+ install
+
+ yarn
+
+
+ generate-resources
+
+ ${skip.npm.install}
+ ${yarn.install.cmd}
+
+
+
+ install-playwrigth
+
+ yarn
+
+
+ generate-resources
+
+ ${skip.npm.install}
+ ${playwright.install.cmd}
+
+
+
+ run node script
+
+
+ yarn
+
+ integration-test
+
+ ./frontend
+ ${e2e.test.skip}
+ ${e2e.test.cmd}
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+
+ ${e2e.test.skip}
+
+
+
+ integration-test
+ none
+
+
+ verify
+
+ verify
+
+
+
+
+
+
+
+ io.fabric8
+ docker-maven-plugin
+
+ true
+ true
+ ${e2e.test.skip}
+
+
+ ${docker.image.wiremock}
+
+
+ wiremock.port:8080
+
+
+
+ ${docker.wm.volume}:${docker.wm.volume.internal}
+
+
+
+ [WireMock]
+ green
+
+
+
+
+
+
+ -XX:+PrintFlagsFinal
+ 15
+ FORCE
+ false
+ false
+ 600
+ obfuscate_me
+ true
+ http://localhost:8080
+ true
+ true
+ http://wm:8080/c
+ http://wm:8080/i
+ http://wm:8080/e
+ http://wm:8080/m
+ true
+
+
+ wiremock:wm
+
+
+
+
+
+
+
+ cleanup-at-start
+
+ stop
+ volume-remove
+
+ pre-integration-test
+
+
+ start
+
+ start
+
+ pre-integration-test
+
+
+ stop
+
+ stop
+
+ verify
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/e2e/pom.xml b/e2e/pom.xml
index 77bd5cd7315c..858fd6756a11 100644
--- a/e2e/pom.xml
+++ b/e2e/pom.xml
@@ -20,7 +20,7 @@
dotcms-e2e-java
-
+ dotcms-e2e-node
diff --git a/pom.xml b/pom.xml
index baf25e35824f..77fd5695e41e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -29,7 +29,7 @@
dotcms-postman
reports
e2e/dotcms-e2e-java
-
+ e2e/dotcms-e2e-node