diff --git a/tests/browserstack_automation/page_objects/footerSocialLinks.browser.js b/tests/browserstack_automation/page_objects/footerSocialLinks.browser.js new file mode 100644 index 000000000..56dcd94c6 --- /dev/null +++ b/tests/browserstack_automation/page_objects/footerSocialLinks.browser.js @@ -0,0 +1,66 @@ +import { $, browser,driver } from '@wdio/globals'; +import PageBrowser from './page.browser'; + +// Page object for the WeVote homepage footer social media links. +// All element IDs confirmed from DOM inspection of quality.wevote.us. +// The homepage uses weVote-prefixed IDs (e.g. weVoteFacebook), +// which are different from the credits page footer (footerLinkFacebook). + +class FooterSocialLinksBrowser extends PageBrowser { + constructor () { + super(); + this.title = 'Ready to Vote? - WeVote'; + } + + // ─── Social link elements ─────────────────────────────────────────────────── + + get facebookLink () { + return $('#weVoteFacebook'); + } + + get instagramLink () { + return $('#weVoteInstagram'); + } + + get twitterLink () { + return $('#weVoteTwitter'); + } + + get tiktokLink () { + return $('#weVoteTikTok'); + } + + get newsletterLink () { + return $('a[href*="eepurl"]'); + } + + get githubLink () { + return $('#weVoteGithub'); + } + + // ─── Page load ────────────────────────────────────────────────────────────── + + async load () { + await super.open('/ready'); + } + + // ─── Helpers ──────────────────────────────────────────────────────────────── + + async scrollToFooter () { + await browser.execute(() => window.scrollTo(0, document.body.scrollHeight)); + await driver.pause(3000); + } + + async getAllSocialLinks () { + return [ + { name: 'Facebook', el: this.facebookLink }, + { name: 'Instagram', el: this.instagramLink }, + { name: 'X/Twitter', el: this.twitterLink }, + { name: 'TikTok', el: this.tiktokLink }, + { name: 'Newsletter', el: this.newsletterLink }, + { name: 'Github', el: this.githubLink }, + ]; + } +} + +export default new FooterSocialLinksBrowser(); \ No newline at end of file diff --git a/tests/browserstack_automation/specs/FooterSocialLinks.browser.js b/tests/browserstack_automation/specs/FooterSocialLinks.browser.js new file mode 100644 index 000000000..6d02f5ae6 --- /dev/null +++ b/tests/browserstack_automation/specs/FooterSocialLinks.browser.js @@ -0,0 +1,160 @@ +import { driver, expect, browser } from '@wdio/globals'; +import FooterSocialLinksPage from '../page_objects/footerSocialLinks.browser'; + +const waitTime = 5000; + +/* eslint-disable no-undef */ +describe('Footer Social Links', () => { + + // TC-001: Social media icons are visible in the footer @BVT + it('verify social media icons are visible in footer @BVT', async () => { + console.log('TC: FooterSocial_001 — social icons visible in footer'); + await FooterSocialLinksPage.load(); + await driver.pause(waitTime); + await FooterSocialLinksPage.scrollToFooter(); + + await FooterSocialLinksPage.facebookLink.waitForDisplayed({ timeout: waitTime }); + + const links = await FooterSocialLinksPage.getAllSocialLinks(); + for (const { name, el } of links) { + await expect(el).toBeDisplayed(`${name} icon is not visible in footer`); + } + }); + + // TC-002: All expected platform icons exist in the footer @BVT + it('verify icons exist for all expected social platforms @BVT', async () => { + console.log('TC: FooterSocial_002 — icons exist for expected platforms'); + await FooterSocialLinksPage.load(); + await driver.pause(waitTime); + await FooterSocialLinksPage.scrollToFooter(); + + await FooterSocialLinksPage.facebookLink.waitForDisplayed({ timeout: waitTime }); + + const links = await FooterSocialLinksPage.getAllSocialLinks(); + for (const { name, el } of links) { + const exists = await el.isExisting(); + expect(exists).toBe(true, `${name} link element not found in footer DOM`); + } + }); + + // TC-005: Facebook icon links to correct profile @BVT + it('verify Facebook icon links to correct profile @BVT', async () => { + console.log('TC: FooterSocial_005 — Facebook href contains facebook.com'); + await FooterSocialLinksPage.load(); + await driver.pause(waitTime); + await FooterSocialLinksPage.scrollToFooter(); + + await FooterSocialLinksPage.facebookLink.waitForDisplayed({ timeout: waitTime }); + const href = await FooterSocialLinksPage.facebookLink.getAttribute('href'); + expect(href).toContain('facebook.com'); + }); + + // TC-006: Instagram icon links to correct profile @BVT + it('verify Instagram icon links to correct profile @BVT', async () => { + console.log('TC: FooterSocial_006 — Instagram href contains instagram.com'); + await FooterSocialLinksPage.load(); + await driver.pause(waitTime); + await FooterSocialLinksPage.scrollToFooter(); + + await FooterSocialLinksPage.instagramLink.waitForDisplayed({ timeout: waitTime }); + const href = await FooterSocialLinksPage.instagramLink.getAttribute('href'); + expect(href).toContain('instagram.com'); + }); + + // TC-007: X/Twitter icon links to correct profile @BVT + it('verify X/Twitter icon links to correct profile @BVT', async () => { + console.log('TC: FooterSocial_007 — Twitter href contains x.com or twitter.com'); + await FooterSocialLinksPage.load(); + await driver.pause(waitTime); + await FooterSocialLinksPage.scrollToFooter(); + + await FooterSocialLinksPage.twitterLink.waitForDisplayed({ timeout: waitTime }); + const href = await FooterSocialLinksPage.twitterLink.getAttribute('href'); + const isValid = href.includes('x.com') || href.includes('twitter.com'); + expect(isValid).toBe(true, `X/Twitter link points to unexpected URL: ${href}`); + }); + + // TC-010: Social media links open in a new tab @BVT + it('verify all social media links open in a new tab @BVT', async () => { + console.log('TC: FooterSocial_010 — social links have target="_blank"'); + await FooterSocialLinksPage.load(); + await driver.pause(waitTime); + await FooterSocialLinksPage.scrollToFooter(); + + await FooterSocialLinksPage.facebookLink.waitForDisplayed({ timeout: waitTime }); + + const links = await FooterSocialLinksPage.getAllSocialLinks(); + for (const { name, el } of links) { + const target = await el.getAttribute('target'); + expect(target).toBe('_blank', `${name} link does not open in a new tab — target='${target}'`); + } + }); + + // TC-011: Original page remains open after clicking a social icon + it('verify original page remains open after clicking Facebook icon', async () => { + console.log('TC: FooterSocial_011 — original tab stays open after click'); + await FooterSocialLinksPage.load(); + await driver.pause(waitTime); + await FooterSocialLinksPage.scrollToFooter(); + + await FooterSocialLinksPage.facebookLink.waitForDisplayed({ timeout: waitTime }); + + const originalWindow = await driver.getWindowHandle(); + const originalUrl = await driver.getUrl(); + + await FooterSocialLinksPage.facebookLink.click(); + await driver.pause(waitTime); + + const allWindows = await driver.getWindowHandles(); + expect(allWindows.length).toBeGreaterThan(1, 'No new tab opened after clicking Facebook icon'); + + // Switch back to original and verify it is unchanged + await driver.switchToWindow(originalWindow); + await expect(driver).toHaveUrl(originalUrl); + }); + + // TC-012: Icons show a pointer cursor on hover + it('verify social icons show pointer cursor on hover', async () => { + console.log('TC: FooterSocial_012 — icons show pointer cursor on hover'); + await FooterSocialLinksPage.load(); + await driver.pause(waitTime); + await FooterSocialLinksPage.scrollToFooter(); + + await FooterSocialLinksPage.facebookLink.waitForDisplayed({ timeout: waitTime }); + + const cursor = await browser.execute((el) => { + return window.getComputedStyle(el).cursor; + }, await FooterSocialLinksPage.facebookLink); + + expect(cursor).toBe('pointer', `Facebook icon cursor is '${cursor}', expected 'pointer'`); + }); + + // TC-015: No broken image icons in the footer social area + it('verify no broken social media icon images in footer', async () => { + console.log('TC: FooterSocial_015 — no broken icon images'); + await FooterSocialLinksPage.load(); + await driver.pause(waitTime); + await FooterSocialLinksPage.scrollToFooter(); + + await FooterSocialLinksPage.facebookLink.waitForDisplayed({ timeout: waitTime }); + + const links = await FooterSocialLinksPage.getAllSocialLinks(); + for (const { name, el } of links) { + const imgs = await el.$$('img'); + for (const img of imgs) { + const naturalWidth = await browser.execute((i) => i.naturalWidth, img); + const src = await img.getAttribute('src') || ''; + expect(naturalWidth).toBeGreaterThan( + 0, + `${name}: broken with src='${src}'` + ); + } + const svgs = await el.$$('svg'); + for (const svg of svgs) { + const size = await svg.getSize(); + expect(size.width).toBeGreaterThan(0, `${name}: has zero width (not rendered)`); + } + } + }); + +}); \ No newline at end of file