diff --git a/app/rehabilitation/page.js b/app/rehabilitation/page.js
index 4ab032d..fb82dc9 100644
--- a/app/rehabilitation/page.js
+++ b/app/rehabilitation/page.js
@@ -51,7 +51,7 @@ export default function RehabilitationPage() {
-
+
diff --git a/package-lock.json b/package-lock.json
index 2ba5f5b..14b29e9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,7 +8,7 @@
"name": "start-app-dir",
"version": "0.1.0",
"dependencies": {
- "axios": "^1.11.0",
+ "axios": "^1.13.2",
"framer-motion": "^12.23.12",
"imagemin": "^9.0.1",
"imagemin-avif": "^0.1.6",
@@ -23,8 +23,10 @@
"react-google-recaptcha": "^3.1.0",
"react-modal-video": "^2.0.1",
"sass": "^1.66.1",
+ "selenium-webdriver": "^4.38.0",
"swiper": "^10.2.0",
- "wowjs": "^1.1.3"
+ "wowjs": "^1.1.3",
+ "xml2js": "^0.6.2"
},
"devDependencies": {
"next-export-optimize-images": "^4.7.0",
@@ -40,6 +42,12 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@bazel/runfiles": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/@bazel/runfiles/-/runfiles-6.5.0.tgz",
+ "integrity": "sha512-RzahvqTkfpY2jsDxo8YItPX+/iZ6hbiikw1YhE0bA9EKBR5Og8Pa6FHn9PO9M0zaXRVsr0GFQLKbB/0rzy9SzA==",
+ "license": "Apache-2.0"
+ },
"node_modules/@borewit/text-codec": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.1.1.tgz",
@@ -1185,9 +1193,9 @@
}
},
"node_modules/axios": {
- "version": "1.11.0",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz",
- "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==",
+ "version": "1.13.2",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz",
+ "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
@@ -3506,6 +3514,12 @@
"node": ">=14.16"
}
},
+ "node_modules/immediate": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
+ "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
+ "license": "MIT"
+ },
"node_modules/immutable": {
"version": "5.1.3",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.3.tgz",
@@ -3826,6 +3840,18 @@
"graceful-fs": "^4.1.6"
}
},
+ "node_modules/jszip": {
+ "version": "3.10.1",
+ "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
+ "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
+ "license": "(MIT OR GPL-3.0-or-later)",
+ "dependencies": {
+ "lie": "~3.3.0",
+ "pako": "~1.0.2",
+ "readable-stream": "~2.3.6",
+ "setimmediate": "^1.0.5"
+ }
+ },
"node_modules/junk": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/junk/-/junk-4.0.1.tgz",
@@ -3853,6 +3879,15 @@
"json-buffer": "3.0.0"
}
},
+ "node_modules/lie": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
+ "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
+ "license": "MIT",
+ "dependencies": {
+ "immediate": "~3.0.5"
+ }
+ },
"node_modules/lodash.uniqwith": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.uniqwith/-/lodash.uniqwith-4.5.0.tgz",
@@ -4852,6 +4887,12 @@
"node": ">=4"
}
},
+ "node_modules/pako": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
+ "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
+ "license": "(MIT AND Zlib)"
+ },
"node_modules/path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
@@ -5334,7 +5375,6 @@
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
"integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
- "dev": true,
"license": "ISC"
},
"node_modules/scheduler": {
@@ -5359,6 +5399,31 @@
"seek-table": "bin/seek-bzip-table"
}
},
+ "node_modules/selenium-webdriver": {
+ "version": "4.38.0",
+ "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.38.0.tgz",
+ "integrity": "sha512-5/UXXFSQmn7FGQkbcpAqvfhzflUdMWtT7QqpEgkFD6Q6rDucxB5EUfzgjmr6JbUj30QodcW3mDXehzoeS/Vy5w==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/SeleniumHQ"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/selenium"
+ }
+ ],
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@bazel/runfiles": "^6.3.1",
+ "jszip": "^3.10.1",
+ "tmp": "^0.2.5",
+ "ws": "^8.18.3"
+ },
+ "engines": {
+ "node": ">= 20.0.0"
+ }
+ },
"node_modules/semver": {
"version": "5.7.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
@@ -5406,6 +5471,12 @@
"node": ">= 0.4"
}
},
+ "node_modules/setimmediate": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
+ "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==",
+ "license": "MIT"
+ },
"node_modules/sharp": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz",
@@ -5814,6 +5885,15 @@
"node": ">=0.10.0"
}
},
+ "node_modules/tmp": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz",
+ "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.14"
+ }
+ },
"node_modules/to-buffer": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.1.tgz",
@@ -6051,6 +6131,49 @@
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"license": "ISC"
},
+ "node_modules/ws": {
+ "version": "8.18.3",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
+ "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/xml2js": {
+ "version": "0.6.2",
+ "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz",
+ "integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==",
+ "license": "MIT",
+ "dependencies": {
+ "sax": ">=0.6.0",
+ "xmlbuilder": "~11.0.0"
+ },
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/xmlbuilder": {
+ "version": "11.0.1",
+ "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
+ "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
"node_modules/xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
diff --git a/package.json b/package.json
index 0f180ca..5e9f34e 100644
--- a/package.json
+++ b/package.json
@@ -13,7 +13,7 @@
"postbuild": "npm run optimize-images"
},
"dependencies": {
- "axios": "^1.11.0",
+ "axios": "^1.13.2",
"framer-motion": "^12.23.12",
"imagemin": "^9.0.1",
"imagemin-avif": "^0.1.6",
@@ -28,8 +28,10 @@
"react-google-recaptcha": "^3.1.0",
"react-modal-video": "^2.0.1",
"sass": "^1.66.1",
+ "selenium-webdriver": "^4.38.0",
"swiper": "^10.2.0",
- "wowjs": "^1.1.3"
+ "wowjs": "^1.1.3",
+ "xml2js": "^0.6.2"
},
"devDependencies": {
"next-export-optimize-images": "^4.7.0",
diff --git a/scripts/image_alt_issues.csv b/scripts/image_alt_issues.csv
new file mode 100644
index 0000000..802ae04
--- /dev/null
+++ b/scripts/image_alt_issues.csv
@@ -0,0 +1,97 @@
+Page URL,Image Src,Alt Text,Issue Type
+"http://localhost:3000/","","rapharehab logo","Duplicate Alt (2 times)"
+"http://localhost:3000/","","Decorative Shape","Duplicate Alt (3 times)"
+"http://localhost:3000/","","Physiotherapy Icon","Duplicate Alt (2 times)"
+"http://localhost:3000/why-rapha-physiotherapy-etobicoke/","","rapharehab","Duplicate Alt (3 times)"
+"http://localhost:3000/why-rapha-physiotherapy-etobicoke/","","Physiotherapy at Rapharehab","Duplicate Alt (6 times)"
+"http://localhost:3000/why-rapha-physiotherapy-etobicoke/","","health care professionals","Duplicate Alt (3 times)"
+"http://localhost:3000/why-rapha-physiotherapy-etobicoke/","","Top-Notch Treatment","Duplicate Alt (3 times)"
+"http://localhost:3000/faq-physiotherapy-etobicoke/","","rapharehab","Duplicate Alt (3 times)"
+"http://localhost:3000/what-to-expect/","","rapharehab","Duplicate Alt (3 times)"
+"http://localhost:3000/what-to-expect/","","Physiotherapy at Rapharehab","Duplicate Alt (7 times)"
+"http://localhost:3000/payment-insurance/","","rapharehab","Duplicate Alt (3 times)"
+"http://localhost:3000/payment-insurance/","","Payment and Insurance","Duplicate Alt (3 times)"
+"http://localhost:3000/locations/","","rapharehab","Duplicate Alt (3 times)"
+"http://localhost:3000/about-us/","","rapharehab","Duplicate Alt (3 times)"
+"http://localhost:3000/about-us/","","Physiotherapy at Rapharehab","Duplicate Alt (19 times)"
+"http://localhost:3000/ourapproach-physiotherapy-etobicoke/","","rapharehab","Duplicate Alt (3 times)"
+"http://localhost:3000/ourapproach-physiotherapy-etobicoke/","","Physiotherapy at Rapharehab","Duplicate Alt (8 times)"
+"http://localhost:3000/our-team-physiotherapy-etobicoke/","","rapharehab","Duplicate Alt (3 times)"
+"http://localhost:3000/gallery-physiotherapy-etobicoke/","","rapharehab","Duplicate Alt (3 times)"
+"http://localhost:3000/covid-19-updates/","","rapharehab","Duplicate Alt (3 times)"
+"http://localhost:3000/etobicoke-treatment-service/","","rapharehab","Duplicate Alt (3 times)"
+"http://localhost:3000/etobicoke-treatment-service/","","Osteopathy Clinic In Etobicoke | Rapharehab","Duplicate Alt (2 times)"
+"http://localhost:3000/etobicoke-treatment-service/","","Massage Therapy Clinic in Etobicoke | Rapharehab","Duplicate Alt (2 times)"
+"http://localhost:3000/etobicoke-treatment-service/","","Acupuncture Clinic in Etobicoke | Rapharehab","Duplicate Alt (2 times)"
+"http://localhost:3000/etobicoke-treatment-service/","","Chiropodist Clinic in Etobicoke | Rapharehab","Duplicate Alt (2 times)"
+"http://localhost:3000/etobicoke-treatment-service/","","Chiropractic Clinic in Etobicoke | Rapharehab","Duplicate Alt (2 times)"
+"http://localhost:3000/etobicoke-treatment-service/","","orthotics in etobicoke | Rapharehab","Duplicate Alt (2 times)"
+"http://localhost:3000/etobicoke-treatment-service/","","foot reflexology clinic in etobicoke | Rapharehab","Duplicate Alt (2 times)"
+"http://localhost:3000/etobicoke-treatment-service/","","custom knee braces clinic in etobicoke | Rapharehab","Duplicate Alt (2 times)"
+"http://localhost:3000/etobicoke-treatment-service/","","laser therapy electrical modalities | Rapharehab","Duplicate Alt (2 times)"
+"http://localhost:3000/etobicoke-treatment-service/","","virtual care physiotherapy clinic in etobicoke | Rapharehab","Duplicate Alt (2 times)"
+"http://localhost:3000/etobicoke-treatment-service/","","naturopathy clinic in etobicoke | Rapharehab","Duplicate Alt (2 times)"
+"http://localhost:3000/etobicoke-treatment-service/","","spinal decompression therapy clinic in etobicoke | Rapharehab","Duplicate Alt (2 times)"
+"http://localhost:3000/etobicoke-treatment-service/","","concussion management clinic in etobicoke | Rapharehab","Duplicate Alt (2 times)"
+"http://localhost:3000/etobicoke-treatment-service/","","Chronic Pain Management Clinic in Etobicoke | Rapharehab","Duplicate Alt (2 times)"
+"http://localhost:3000/etobicoke-treatment-service/","","prepost operative management clinic in etobicoke | Rapharehab","Duplicate Alt (2 times)"
+"http://localhost:3000/etobicoke-treatment-service/","","ExerciseTherapy Clinic In Etobicoke | Rapharehab","Duplicate Alt (2 times)"
+"http://localhost:3000/etobicoke-treatment-service/","","Workplace Injury Management Clinic in Etobicoke | Rapharehab","Duplicate Alt (2 times)"
+"http://localhost:3000/etobicoke-treatment-service/","","pelvic floor physiotherapy clinic in etobicoke | Rapharehab","Duplicate Alt (2 times)"
+"http://localhost:3000/etobicoke-treatment-service/","","home care physiotherapy clinic in etobicoke | Rapharehab","Duplicate Alt (2 times)"
+"http://localhost:3000/etobicoke-treatment-service/","","Sports Injury Physiotherapy Clinic in Etobicoke | Rapharehab","Duplicate Alt (2 times)"
+"http://localhost:3000/etobicoke-treatment-service/","","Gait Assessment | Rapharehab","Duplicate Alt (2 times)"
+"http://localhost:3000/etobicoke-treatment-service/","","kids physiotherapy in etobicoke | Rapharehab","Duplicate Alt (2 times)"
+"http://localhost:3000/etobicoke-treatment-service/","","surgical rehab in etobicoke | Rapharehab","Duplicate Alt (2 times)"
+"http://localhost:3000/etobicoke-treatment-service/","","Physiotherapy Clinic in Etobicoke | Rapharehab","Duplicate Alt (2 times)"
+"http://localhost:3000/etobicoke-treatment-service/","","Fascial Stretch Therapy in Etobicoke | Rapharehab","Duplicate Alt (2 times)"
+"http://localhost:3000/etobicoke-treatment-service/","","cupping therapy in etobicoke | Rapharehab","Duplicate Alt (2 times)"
+"http://localhost:3000/etobicoke-treatment-service/","","Shiatsu Therapy in Etobicoke | Rapharehab","Duplicate Alt (2 times)"
+"http://localhost:3000/etobicoke-treatment-service/","","Deep Tissue Massage in Etobicoke | Rapharehab","Duplicate Alt (2 times)"
+"http://localhost:3000/etobicoke-treatment-service/","","neuro fascial therapy in etobicoke | Rapharehab","Duplicate Alt (2 times)"
+"http://localhost:3000/etobicoke-treatment-service/","","Cranio Sacral Therapy in Etobicoke | Rapharehab","Duplicate Alt (2 times)"
+"http://localhost:3000/etobicoke-treatment-service/","","motor vehicle accident rehabilitation in etobicoke | Rapharehab","Duplicate Alt (2 times)"
+"http://localhost:3000/etobicoke-treatment-service/","","Compression Stockings in Etobicoke | Rapharehab","Duplicate Alt (2 times)"
+"http://localhost:3000/etobicoke-treatment-service/","","Cardiac Rehabilitation in Etobicoke | Rapharehab","Duplicate Alt (2 times)"
+"http://localhost:3000/etobicoke-treatment-service/","","Intramuscular Stimulation IMS in Etobicoke | Rapharehab","Duplicate Alt (2 times)"
+"http://localhost:3000/etobicoke-treatment-service/","","Psychotherapy in Etobicoke | Rapharehab","Duplicate Alt (2 times)"
+"http://localhost:3000/etobicoke-treatment-service/","","Shockwave Therapy in Etobicoke | Rapharehab","Duplicate Alt (2 times)"
+"http://localhost:3000/etobicoke-treatment-service/","","refugee in etobicoke | Rapharehab","Duplicate Alt (2 times)"
+"http://localhost:3000/area-of-injury/","","rapharehab","Duplicate Alt (3 times)"
+"http://localhost:3000/area-of-injury/","","HEAD INJURIES","Duplicate Alt (2 times)"
+"http://localhost:3000/area-of-injury/","","NECK INJURIES","Duplicate Alt (2 times)"
+"http://localhost:3000/area-of-injury/","","SHOULDER INJURIES","Duplicate Alt (2 times)"
+"http://localhost:3000/area-of-injury/","","ELBOW INJURIES","Duplicate Alt (2 times)"
+"http://localhost:3000/area-of-injury/","","WRIST & HAND INJURIES","Duplicate Alt (2 times)"
+"http://localhost:3000/area-of-injury/","","LOW BACK INJURIES","Duplicate Alt (2 times)"
+"http://localhost:3000/area-of-injury/","","HIP INJURIES","Duplicate Alt (2 times)"
+"http://localhost:3000/area-of-injury/","","LEG & KNEE INJURIES","Duplicate Alt (2 times)"
+"http://localhost:3000/area-of-injury/","","ANKLE & FOOT INJURIES","Duplicate Alt (2 times)"
+"http://localhost:3000/rehabilitation/","","rapharehab","Duplicate Alt (3 times)"
+"http://localhost:3000/accident/","","rapharehab","Duplicate Alt (3 times)"
+"http://localhost:3000/accident/","","Minor Injury","Duplicate Alt (2 times)"
+"http://localhost:3000/accident/","","Catastrophic Injury","Duplicate Alt (2 times)"
+"http://localhost:3000/accident/","","Slip and Fall Injury","Duplicate Alt (2 times)"
+"http://localhost:3000/accident/","","Concussion Management","Duplicate Alt (2 times)"
+"http://localhost:3000/accident/","","Psychotherapy Management","Duplicate Alt (2 times)"
+"http://localhost:3000/accident/","","Hydrotherapy","Duplicate Alt (2 times)"
+"http://localhost:3000/accident/","","Chronic Pain Management","Duplicate Alt (2 times)"
+"http://localhost:3000/blog/","","rapharehab","Duplicate Alt (3 times)"
+"http://localhost:3000/contact/","","rapharehab","Duplicate Alt (3 times)"
+"http://localhost:3000/caregivers/","","rapharehab","Duplicate Alt (3 times)"
+"http://localhost:3000/caregivers/","","Physiotherapy at Rapharehab","Duplicate Alt (3 times)"
+"http://localhost:3000/shortcodes/","","rapharehab","Duplicate Alt (3 times)"
+"http://localhost:3000/shortcodes/","","Physiotherapy at Rapharehab","Duplicate Alt (6 times)"
+"http://localhost:3000/shortcodes/","","Why choose nanocare","Duplicate Alt (2 times)"
+"http://localhost:3000/blog/chronic-pain-treatment-etobicoke/","","rapharehab","Duplicate Alt (3 times)"
+"http://localhost:3000/blog/chronic-pain-treatment-etobicoke/","","How Does Rapharehab - Physiotherapy in Etobicoke Treat Chronic Pain Conditions?","Duplicate Alt (3 times)"
+"http://localhost:3000/etobicoke-treatment-service/osteopathy-clinic-in-etobicoke/","","rapharehab","Duplicate Alt (3 times)"
+"http://localhost:3000/etobicoke-treatment-service/osteopathy-clinic-in-etobicoke/","","OSTEOPATHY IN ETOBICOKE","Duplicate Alt (2 times)"
+"http://localhost:3000/etobicoke-treatment-service/osteopathy-clinic-in-etobicoke/","","Osteopathy Clinic In Etobicoke | Rapharehab","Duplicate Alt (2 times)"
+"http://localhost:3000/area-of-injury/head-injury-physiotherapy-management-in-etobicoke/","","rapharehab","Duplicate Alt (3 times)"
+"http://localhost:3000/area-of-injury/head-injury-physiotherapy-management-in-etobicoke/","","HEAD INJURIES","Duplicate Alt (5 times)"
+"http://localhost:3000/rehabilitation/spinalcord-rehabilitation-clinic-in-etobicoke/","","rapharehab","Duplicate Alt (3 times)"
+"http://localhost:3000/rehabilitation/spinalcord-rehabilitation-clinic-in-etobicoke/","","Spinal Cord Injury Rehabilitation","Duplicate Alt (2 times)"
+"http://localhost:3000/accident/minor-injury-care-in-etobicoke/","","rapharehab","Duplicate Alt (3 times)"
+"http://localhost:3000/accident/minor-injury-care-in-etobicoke/","","Minor Injury","Duplicate Alt (2 times)"
+"http://localhost:3000/our-team-physiotherapy-etobicoke/dhanya-prashant/","","rapharehab","Duplicate Alt (3 times)"
diff --git a/scripts/seo-test-selenium.cjs b/scripts/seo-test-selenium.cjs
new file mode 100644
index 0000000..b38c90d
--- /dev/null
+++ b/scripts/seo-test-selenium.cjs
@@ -0,0 +1,266 @@
+// š Full SEO + Broken Link + 404 + Accessibility + Image Alt CSV Export
+// Run with: node seo_full_audit.js
+
+const { Builder, By } = require("selenium-webdriver");
+const chrome = require("selenium-webdriver/chrome");
+const axios = require("axios");
+const xml2js = require("xml2js");
+const fs = require("fs");
+const path = require("path");
+
+// CSV file for Image Alt issues
+const csvPath = path.join(__dirname, "image_alt_issues.csv");
+fs.writeFileSync(csvPath, "Page URL,Image Src,Alt Text,Issue Type\n", "utf8");
+
+// ==========================
+// 1ļøā£ Fetch URLs from sitemap.xml
+// ==========================
+async function getUrlsFromSitemap(sitemapUrl) {
+ try {
+ const res = await axios.get(sitemapUrl);
+ const parsed = await xml2js.parseStringPromise(res.data);
+ return parsed.urlset.url.map((u) => u.loc[0]);
+ } catch (err) {
+ console.error("ā Failed to load sitemap:", err.message);
+ return [];
+ }
+}
+
+// ==========================
+// 2ļøā£ Check HTTP Status
+// ==========================
+async function checkLinkStatus(url) {
+ try {
+ const res = await axios.get(url, {
+ timeout: 10000,
+ validateStatus: () => true,
+ });
+
+ if (
+ res.status === 200 &&
+ (
+ /404/i.test(res.data.match(/
]*>(.*?)<\/title>/)?.[1] ?? "")
+ )
+ ) {
+ return "Soft 404";
+ }
+
+ return res.status;
+ } catch (err) {
+ return err.response ? err.response.status : "ā No Response";
+ }
+}
+
+// ==========================
+// 3ļøā£ Main SEO + Accessibility + Image Alt Audit
+// ==========================
+async function checkSEO(url, siteDomain) {
+ const options = new chrome.Options();
+ options.addArguments("--headless", "--no-sandbox", "--disable-gpu");
+
+ const driver = await new Builder()
+ .forBrowser("chrome")
+ .setChromeOptions(options)
+ .build();
+
+ try {
+ const pageStatus = await checkLinkStatus(url);
+ if (pageStatus === 404 || pageStatus === "Soft 404") {
+ console.log(`\nš« ${url} ā ā Page not found (${pageStatus})`);
+ return;
+ }
+
+ await driver.get(url);
+ const pageSource = await driver.getPageSource();
+
+ // Basic SEO Elements
+ const title = await driver.getTitle();
+ const descElem = await driver.findElements(By.css('meta[name="description"]'));
+ const canonicalElem = await driver.findElements(By.css('link[rel="canonical"]'));
+ const robotsElem = await driver.findElements(By.css('meta[name="robots"]'));
+ const viewportElem = await driver.findElements(By.css('meta[name="viewport"]'));
+ const charset = await driver.findElements(By.css('meta[charset]'));
+ const htmlTag = await driver.findElement(By.css("html"));
+ const langAttr = await htmlTag.getAttribute("lang").catch(() => "");
+ const h1Tags = await driver.findElements(By.css("h1"));
+ const h2Tags = await driver.findElements(By.css("h2"));
+
+ // Meta Description
+ let descContent = descElem.length > 0 ? await descElem[0].getAttribute("content") : "";
+ const descLength = descContent.length;
+ const descStatus =
+ descLength === 0
+ ? "ā Missing"
+ : descLength < 50
+ ? `ā ļø Too short (${descLength})`
+ : descLength > 160
+ ? `ā ļø Too long (${descLength})`
+ : "ā
Perfect";
+
+ // Title length check
+ const titleLength = title.length;
+ const titleStatus =
+ titleLength === 0
+ ? "ā Missing"
+ : titleLength < 30
+ ? `ā ļø Too short (${titleLength})`
+ : titleLength > 65
+ ? `ā ļø Too long (${titleLength})`
+ : "ā
Perfect";
+
+ // Canonical
+ const canonicalURL =
+ canonicalElem.length > 0 ? await canonicalElem[0].getAttribute("href") : "ā Missing";
+
+ // š¼ļø Image Accessibility Audit
+ const imgs = await driver.findElements(By.css("img"));
+ let missingAlt = 0;
+ let emptyAlt = 0;
+ let duplicateAlt = [];
+ const altTextMap = new Map();
+
+ for (const img of imgs) {
+ const src = await img.getAttribute("src");
+ const alt = (await img.getAttribute("alt"))?.trim() ?? null;
+
+ if (alt === null) {
+ missingAlt++;
+ fs.appendFileSync(csvPath, `"${url}","${src}","","Missing Alt"\n`, "utf8");
+ continue;
+ }
+
+ if (alt === "") {
+ emptyAlt++;
+ fs.appendFileSync(csvPath, `"${url}","${src}","(empty)","Empty Alt"\n`, "utf8");
+ }
+
+ if (altTextMap.has(alt)) {
+ altTextMap.set(alt, altTextMap.get(alt) + 1);
+ } else {
+ altTextMap.set(alt, 1);
+ }
+ }
+
+ for (const [altText, count] of altTextMap.entries()) {
+ if (altText && count > 1) {
+ duplicateAlt.push({ altText, count });
+ fs.appendFileSync(
+ csvPath,
+ `"${url}","","${altText}","Duplicate Alt (${count} times)"\n`,
+ "utf8"
+ );
+ }
+ }
+
+ // Detect tracking & schema tags
+ const hasGTM = pageSource.includes("googletagmanager.com/gtm.js");
+ const hasClarity = pageSource.includes("clarity.ms/tag");
+ const hasFBPixel = pageSource.includes("fbevents.js") || pageSource.includes("fbq(");
+ const hasAnalytics = pageSource.includes("www.googletagmanager.com/gtag/js");
+
+ const ogTags = await driver.findElements(By.css("meta[property^='og:']"));
+ const twitterTags = await driver.findElements(By.css("meta[name^='twitter:']"));
+ const schemaScripts = await driver.findElements(By.css('script[type="application/ld+json"]'));
+
+ // Links check
+ const anchorTags = await driver.findElements(By.css("a[href]"));
+ const brokenLinks = [];
+ for (const a of anchorTags) {
+ const href = await a.getAttribute("href");
+ if (!href || href.startsWith("#") || href.startsWith("mailto:")) continue;
+
+ const fullUrl = href.startsWith("http")
+ ? href
+ : `${siteDomain}${href.startsWith("/") ? href : `/${href}`}`;
+
+ if (fullUrl.includes(siteDomain)) {
+ const status = await checkLinkStatus(fullUrl);
+ if (status === 404 || status === "Soft 404" || status === "ā No Response") {
+ brokenLinks.push({ link: fullUrl, status });
+ }
+ }
+ }
+
+ // Lazy loading check
+ const images = await driver.findElements(By.css("img, video, iframe"));
+ const lazyLoadCount = await Promise.all(
+ images.map(async (img) => {
+ const loading = await img.getAttribute("loading");
+ return loading === "lazy";
+ })
+ );
+ const lazyLoaded = lazyLoadCount.filter((v) => v).length;
+
+ // Console Summary
+ console.log(`\nš Checking: ${url}`);
+ console.log("-------------------------------------------");
+ console.log("Title:", titleStatus);
+ console.log("Meta Description:", descStatus);
+ console.log("Canonical URL:", canonicalURL);
+ console.log("Meta Robots:", robotsElem.length > 0 ? "ā
Found" : "ā ļø Missing");
+ console.log("Viewport:", viewportElem.length > 0 ? "ā
Found" : "ā ļø Missing");
+ console.log("Charset:", charset.length > 0 ? "ā
Found" : "ā Missing");
+ console.log("HTML lang:", langAttr ? `ā
${langAttr}` : "ā ļø Missing");
+ console.log("H1 Tags:", h1Tags.length > 0 ? `ā
${h1Tags.length}` : "ā Missing");
+ console.log("H2 Tags:", h2Tags.length > 0 ? `ā¹ļø ${h2Tags.length}` : "ā ļø None");
+ console.log("Images:", imgs.length);
+ console.log(
+ "Missing Alt:",
+ missingAlt > 0 ? `ā ${missingAlt}` : "ā
None"
+ );
+ console.log(
+ "Empty Alt:",
+ emptyAlt > 0 ? `ā ļø ${emptyAlt}` : "ā
None"
+ );
+ console.log(
+ "Duplicate Alt:",
+ duplicateAlt.length > 0 ? `ā ļø ${duplicateAlt.length}` : "ā
None"
+ );
+ console.log("Lazy Loaded Images:", lazyLoaded > 0 ? `ā
${lazyLoaded}` : "ā ļø None");
+ console.log("Open Graph Tags:", ogTags.length > 0 ? "ā
Found" : "ā ļø Missing");
+ console.log("Twitter Tags:", twitterTags.length > 0 ? "ā
Found" : "ā ļø Missing");
+ console.log("Schema Markup:", schemaScripts.length > 0 ? "ā
Found" : "ā ļø Missing");
+ console.log("Google Analytics:", hasAnalytics ? "ā
Found" : "ā ļø Missing");
+ console.log("GTM:", hasGTM ? "ā
Found" : "ā ļø Missing");
+ console.log("Clarity:", hasClarity ? "ā
Found" : "ā ļø Missing");
+ console.log("Facebook Pixel:", hasFBPixel ? "ā
Found" : "ā ļø Missing");
+
+ if (brokenLinks.length > 0) {
+ console.log("\nā Broken Links:");
+ brokenLinks.forEach((b) => console.log(` ā ${b.link} [${b.status}]`));
+ } else {
+ console.log("ā
No broken links found.");
+ }
+
+ } catch (err) {
+ console.error(`ā Error on ${url}:`, err.message);
+ } finally {
+ await driver.quit();
+ }
+}
+
+// ==========================
+// 4ļøā£ Run Full Site Audit
+// ==========================
+(async () => {
+ const sitemapUrl = "https://rapharehab.ca/sitemap.xml";
+ const siteDomain = "http://localhost:3000";
+
+ console.log("š Fetching URLs from sitemap...");
+ const urls = await getUrlsFromSitemap(sitemapUrl);
+
+ if (urls.length === 0) {
+ console.error("ā No URLs found in sitemap.");
+ return;
+ }
+
+ console.log(`ā
Found ${urls.length} URLs in sitemap.`);
+ console.log("š Starting Full SEO + Accessibility + Broken Link Audit...");
+
+ for (const url of urls) {
+ await checkSEO(url, siteDomain);
+ }
+
+ console.log("\nā
Full SEO Audit Completed!");
+ console.log(`š CSV Report: ${csvPath}`);
+})();