Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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();
160 changes: 160 additions & 0 deletions tests/browserstack_automation/specs/FooterSocialLinks.browser.js
Original file line number Diff line number Diff line change
@@ -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 <img> with src='${src}'`
);
}
const svgs = await el.$$('svg');
for (const svg of svgs) {
const size = await svg.getSize();
expect(size.width).toBeGreaterThan(0, `${name}: <svg> has zero width (not rendered)`);
}
}
});

});