diff --git a/app/accident/[slug]/page.js b/app/accident/[slug]/page.js index ad7d021..de4a9d1 100644 --- a/app/accident/[slug]/page.js +++ b/app/accident/[slug]/page.js @@ -69,7 +69,7 @@ export default function AccidentDetailsPage({ params }) {
- {service.title} + {service.title}
diff --git a/app/accident/page.js b/app/accident/page.js index 75b9f6d..6929ea3 100644 --- a/app/accident/page.js +++ b/app/accident/page.js @@ -50,7 +50,7 @@ export default function AccidentPage() {
- {service.title} + {service.title}
diff --git a/app/area-of-injury/[slug]/page.js b/app/area-of-injury/[slug]/page.js index 91c9391..61ab2e5 100644 --- a/app/area-of-injury/[slug]/page.js +++ b/app/area-of-injury/[slug]/page.js @@ -92,7 +92,7 @@ export default function AreaOfInjuryDetails({ params }) {
{/* Main Image */}
- {altText} + {altText}

{service.shortTitle}

diff --git a/app/area-of-injury/page.js b/app/area-of-injury/page.js index 9328e11..6aaa629 100644 --- a/app/area-of-injury/page.js +++ b/app/area-of-injury/page.js @@ -55,7 +55,7 @@ export default function AreaOfInjury() {
- {altText} + {altText}
diff --git a/app/blog/[slug]/page.js b/app/blog/[slug]/page.js index c649b9d..d41af54 100644 --- a/app/blog/[slug]/page.js +++ b/app/blog/[slug]/page.js @@ -82,6 +82,7 @@ export default function BlogDetails({ params }) { {related.title}
diff --git a/app/blog/page.js b/app/blog/page.js index cba6c08..a8d529c 100644 --- a/app/blog/page.js +++ b/app/blog/page.js @@ -29,7 +29,7 @@ export default function Blog() {
- {blog.title} + {blog.title}
diff --git a/app/contact/ContactClient.js b/app/contact/ContactClient.js index 5cbd77e..62795ed 100644 --- a/app/contact/ContactClient.js +++ b/app/contact/ContactClient.js @@ -134,7 +134,7 @@ export default function ContactClient() {

Mailing Address

- + Address icon

5 – 4335 Bloor Street West
Etobicoke, M9C 2A5

diff --git a/app/covid-19-updates/CovidClient.js b/app/covid-19-updates/CovidClient.js index 1ebf214..603254d 100644 --- a/app/covid-19-updates/CovidClient.js +++ b/app/covid-19-updates/CovidClient.js @@ -64,7 +64,7 @@ export default function Home() { >
- Virtual Therapy + Virtual Therapy

VIRTUAL THERAPY

@@ -88,7 +88,7 @@ export default function Home() {

- Home Therapy + Home Therapy
diff --git a/app/etobicoke-treatment-service/[slug]/page.js b/app/etobicoke-treatment-service/[slug]/page.js index 4710136..023c1a2 100644 --- a/app/etobicoke-treatment-service/[slug]/page.js +++ b/app/etobicoke-treatment-service/[slug]/page.js @@ -89,7 +89,7 @@ export default function ServiceDetailPage({ params }) {
- {altText} + {altText}
- {item.alt} + {item.alt}
diff --git a/app/faq-physiotherapy-etobicoke/FaqClient.js b/app/faq-physiotherapy-etobicoke/FaqClient.js index 224f573..31ab0a0 100644 --- a/app/faq-physiotherapy-etobicoke/FaqClient.js +++ b/app/faq-physiotherapy-etobicoke/FaqClient.js @@ -41,6 +41,7 @@ export default function Faq() { Do You Have Any Physiotherapy Questions diff --git a/app/gallery-physiotherapy-etobicoke/GalleryClient.js b/app/gallery-physiotherapy-etobicoke/GalleryClient.js index ba695d8..918962e 100644 --- a/app/gallery-physiotherapy-etobicoke/GalleryClient.js +++ b/app/gallery-physiotherapy-etobicoke/GalleryClient.js @@ -24,13 +24,17 @@ export default function WhyChooseUs() { <>
+
+ Gallery +

Empowering Every Movement

+
{currentImages.map((src, index) => (
- {`gallery-${index}`} + {`gallery-${index}`}
@@ -53,7 +57,7 @@ export default function WhyChooseUs() { e.preventDefault() handlePageChange(currentPage - 1) }} - aria-label="Gallery left image"> + aria-label="Gallery left image"> Previous @@ -68,7 +72,7 @@ export default function WhyChooseUs() { handlePageChange(i + 1) }} className={currentPage === i + 1 ? "current" : ""} - aria-label="Gallery list"> + aria-label="Gallery list"> {i + 1} @@ -82,7 +86,7 @@ export default function WhyChooseUs() { e.preventDefault() handlePageChange(currentPage + 1) }} - aria-label="Gallery right image"> + aria-label="Gallery right image"> Previous diff --git a/app/layout.js b/app/layout.js index 20f0634..4dffb57 100644 --- a/app/layout.js +++ b/app/layout.js @@ -36,6 +36,7 @@ export default function RootLayout({ children }) { {/* āœ… Canonical Tag */} + {/* āœ… Open Graph Meta */} diff --git a/app/our-team-physiotherapy-etobicoke/TeamClient.js b/app/our-team-physiotherapy-etobicoke/TeamClient.js index d73cb44..dca42a4 100644 --- a/app/our-team-physiotherapy-etobicoke/TeamClient.js +++ b/app/our-team-physiotherapy-etobicoke/TeamClient.js @@ -27,6 +27,7 @@ export default function Home() { {member.name} diff --git a/app/our-team-physiotherapy-etobicoke/[slug]/page.js b/app/our-team-physiotherapy-etobicoke/[slug]/page.js index b255800..e2f18a9 100644 --- a/app/our-team-physiotherapy-etobicoke/[slug]/page.js +++ b/app/our-team-physiotherapy-etobicoke/[slug]/page.js @@ -61,7 +61,7 @@ export default function TeamDetails({ params }) {
- {member.name} + {member.name}
diff --git a/app/payment-insurance/PaymentInsurence.js b/app/payment-insurance/PaymentInsurence.js index d9ea4fa..d892aa7 100644 --- a/app/payment-insurance/PaymentInsurence.js +++ b/app/payment-insurance/PaymentInsurence.js @@ -350,9 +350,9 @@ export default function RefugeeIFHP() {
-
Payment and Insurance
-
Payment and Insurance
-
Payment and Insurance
+
Payment and Insurance
+
Payment and Insurance
+
Payment and Insurance
diff --git a/app/rehabilitation/[slug]/page.js b/app/rehabilitation/[slug]/page.js index 47ebfd9..14d70d0 100644 --- a/app/rehabilitation/[slug]/page.js +++ b/app/rehabilitation/[slug]/page.js @@ -64,7 +64,7 @@ export default function RehabilitationDetailsPage({ params }) {
- {service.title} + {service.title}
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() {
- {service.title} + {service.title}
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}`); +})();