Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closes #129 Automate the scenario that check LL isnot applied on LCP/ATF images #144

Open
wants to merge 14 commits into
base: develop
Choose a base branch
from
Open
3 changes: 1 addition & 2 deletions backstop.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@
"height": 1080
}
],
"scenarios": [
],
"scenarios": [],
"paths": {
"bitmaps_reference": "backstop_data/bitmaps_reference",
"bitmaps_test": "backstop_data/bitmaps_test",
Expand Down
13 changes: 12 additions & 1 deletion config/wp.config.sample.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,16 @@ const WP_ADMIN_USER = {

} as const;

/**
* The default Imagify settings information
*
* @constant
* @type {{ apiKey: string }}
*/
const IMAGIFY_INFOS = {
apiKey: ''
} as const;

/**
* Extracted environment variables related to WordPress configuration.
* Uses default values if environment variables are not set.
Expand Down Expand Up @@ -99,5 +109,6 @@ export {
WP_SSH_ADDRESS,
WP_SSH_KEY,
WP_SSH_ROOT_DIR,
SCENARIO_URLS
SCENARIO_URLS,
IMAGIFY_INFOS
};
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"lint:fix": "eslint . --ext .ts --fix",
"test:e2e": "$npm_package_config_testCommand",
"test:smoke": "$npm_package_config_testCommand --tags @smoke",
"test:lcpll": "$npm_package_config_testCommand --tags @lcpll",
"test:local": "$npm_package_config_testCommand --tags @local",
"test:online": "$npm_package_config_testCommand --tags @online",
"test:vr": "$npm_package_config_testCommand --tags @vr",
Expand Down
43 changes: 43 additions & 0 deletions src/features/ll-lcp.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
@lcpll @delaylcp @setup
Feature: Lazyload with LCP

Background:
Given I am logged in
And plugin is installed 'new_release'
And plugin 'wp-rocket' is activated
When I go to 'wp-admin/options-general.php?page=wprocket#dashboard'
And I save settings 'media' 'lazyloadCssBgImg'

Scenario: Should Exclude LCP/ATF from Lazyload
And I clear cache
When I log out
And I visit the urls for 'desktop'
When I am logged in
And I clear cache
And I visit the urls and check for lazyload
Then lcp and atf images are not written to LL format

Scenario: Should exclude next-gen lcp/atf from LL
Given I install plugin 'imagify'
And plugin 'imagify' is activated
When I am logged in
And Imagify is set up
When I log out
And I visit page 'lcp_with_imagify' and check for lcp
When I am logged in
And I save settings 'media' 'lazyloadCssBgImg'
And I clear cache
And I visit the 'lcp_with_imagify' and check lcp-atf are not lazyloaded
Then lcp and atf images are not written to LL format

Scenario: Should exclude next-gen lcp/atf from LL
When I am logged in
And display next-gen is enabled on imagify
When I log out
And I visit page 'lcp_with_imagify' and check for lcp
When I am logged in
And I save settings 'media' 'lazyloadCssBgImg'
And I clear cache
And I visit the 'lcp_with_imagify' and check lcp-atf are not lazyloaded
Then lcp and atf images are not written to LL format

3 changes: 2 additions & 1 deletion src/support/steps/general.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,8 @@ When('I clear cache', async function (this:ICustomWorld) {
await this.utils.gotoWpr();

this.sections.set('dashboard');
await this.sections.toggle('clearCacheBtn');
await this.page.locator("text=Clear and preload").last().click();
//await this.sections.toggle('clearCacheBtn');
await expect(this.page.getByText('WP Rocket: Cache cleared.')).toBeVisible();
});

Expand Down
31 changes: 31 additions & 0 deletions src/support/steps/imagify.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { ICustomWorld } from "../../common/custom-world";

import { Given } from '@cucumber/cucumber';
import { IMAGIFY_INFOS } from "../../../config/wp.config";
import {expect} from "@playwright/test";

Given('Imagify is set up', async function (this: ICustomWorld) {
await this.utils.gotoImagify();

// Check if the API key input field exists on the page
const apiKeyInput = await this.page.$('input#api_key');

if (apiKeyInput) {
// Fill the API key input field with the API key from the config
await this.page.fill('input#api_key', IMAGIFY_INFOS.apiKey);
// Click the submit button to save the changes
await this.page.click('div.submit.imagify-clearfix input#submit');
}
});
Given('display next-gen is enabled on imagify', async function (this: ICustomWorld) {
// Go to Imagify setting page
await this.utils.gotoImagify();

// Check the 'Display images in Next-Gen format on the site' checkbox
await this.page.click('label[for="imagify_display_nextgen"]');

// Click the submit button to save the changes
await this.page.click('input#submit');

await expect(this.page.getByText('Settings saved.')).toBeVisible();
});
180 changes: 167 additions & 13 deletions src/support/steps/lcp-beacon-script.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,63 @@
* @requires {@link @playwright/test}
* @requires {@link @cucumber/cucumber}
*/
import { ICustomWorld } from "../../common/custom-world";
import { expect } from "@playwright/test";
import { When, Then } from "@cucumber/cucumber";
import { LcpData, Row } from "../../../utils/types";

import { dbQuery, getWPTablePrefix } from "../../../utils/commands";
import { extractFromStdout } from "../../../utils/helpers";
import { WP_BASE_URL } from '../../../config/wp.config';
import {ICustomWorld} from "../../common/custom-world";
import {expect} from "@playwright/test";
import {Then, When} from "@cucumber/cucumber";
import {LcpData, Row, SinglePageLCPImages} from "../../../utils/types";

import {dbQuery, getWPTablePrefix} from "../../../utils/commands";
import {extractFromStdout} from "../../../utils/helpers";
import {WP_BASE_URL} from '../../../config/wp.config';
import fs from 'fs/promises';

let data: string,
truthy: boolean = true,
failMsg: string,
jsonData: Record<string, { lcp: string[]; viewport: string[]; enabled: boolean, comment: string; }>,
isDbResultAvailable: boolean = true;
isDbResultAvailable: boolean = true,
lcpLLImages: { [key: string] : { src: string; url: string | boolean; lazyloaded: string | boolean }} = {},
singlePageLcp : SinglePageLCPImages = {url: '', lcp: '', viewport: ''};

const actual: LcpData = {};

/**
* Executes step to visit page based on the templates and get check for lazyload.
*/
When('I visit the urls and check for lazyload', async function (this: ICustomWorld) {
const resultFile: string = './src/support/results/expectedResultsDesktop.json';

await this.page.setViewportSize({
width: 1600,
height: 700
});

data = await fs.readFile(resultFile, 'utf8');
jsonData = JSON.parse(data);

// Visit page.
for (const key in jsonData) {
if ( jsonData[key].enabled === true ) {
// Visit the page url.
await this.utils.visitPage(key);

lcpLLImages = await this.page.evaluate((url) => {
const images = document.querySelectorAll('img'),
result = {};

Array.from(images).forEach((img) => {
result[url] = {
src: img.getAttribute('src'),
url: url,
lazyloaded: img.classList.contains('lazyloaded')
}
});

return result;
}, key);
}
}
});
/**
* Executes step to visit page based on the form factor(desktop/mobile) and get the LCP/ATF data from DB.
*/
Expand Down Expand Up @@ -71,7 +110,7 @@ When('I visit the urls for {string}', async function (this: ICustomWorld, formFa
await this.page.waitForFunction(() => {
const beacon = document.querySelector('[data-name="wpr-wpr-beacon"]');
return beacon && beacon.getAttribute('beacon-completed') === 'true';
}, { timeout: 900000 });
}, { timeout: 100000 });

if (formFactor !== 'desktop') {
isMobile = 1;
Expand Down Expand Up @@ -152,22 +191,137 @@ Then('lcp and atf should be as expected for {string}', async function (this: ICu
expect(truthy).toBeTruthy();
});

let lcpImages: Array<{ src: string; fetchpriority: string | boolean; lazyloaded: string | boolean }> = [];

Then('lcp image should have fetchpriority', async function (this: ICustomWorld) {
truthy= false;

const imageWithFetchPriority = await this.page.evaluate(() => {
lcpImages = await this.page.evaluate(() => {
const images = document.querySelectorAll('img');
return Array.from(images).map(img => ({
src: img.getAttribute('src'),
fetchpriority: img.getAttribute('fetchpriority') || false
fetchpriority: img.getAttribute('fetchpriority') || false,
lazyloaded: img.classList.contains('lazyloaded')
}));
});

for (const image of imageWithFetchPriority) {
for (const image of lcpImages) {
if(image.src === '/wp-content/rocket-test-data/images/600px-Mapang-test.gif' && image.fetchpriority !== false) {
truthy = true
}
}

expect(truthy).toBeTruthy();
});

Then('lcp and atf images are not written to LL format', async function (this: ICustomWorld) {
// Reset truthy to true here.
truthy = true;

// Iterate over the data
for (const key in jsonData) {
if (Object.hasOwnProperty.call(jsonData, key) && jsonData[key].enabled === true) {
const expected = jsonData[key];
// Check for LCP
for (const lcp of expected.lcp) {
// Check if expected lcp is present in actual lcp.
if (lcpLLImages[key].src.includes(lcp) && lcpLLImages[key].lazyloaded) {
truthy = false;
failMsg += `Expected LCP for - ${lcp} for ${lcpLLImages[key].url} is lazyloaded - ${lcpLLImages[key].src}\n\n\n`;
}
}

// Check for ATF
for (const viewport of expected.viewport) {
if (lcpLLImages[key].src.includes(viewport) && lcpLLImages[key].lazyloaded) {
truthy = false;
failMsg += `Expected Viewport for - ${viewport} for ${lcpLLImages[key].url} is lazyloaded - ${lcpLLImages[key].src}
\n\n\n`;
}
}
}
}

// Fail test when there is expectation mismatch.
expect(truthy).toBeTruthy();
});

When('I visit the {string} and check lcp-atf are not lazyloaded', async function (this: ICustomWorld, url: string) {
// Reset truthy to true here.
truthy = true;

await this.page.setViewportSize({
width: 1600,
height: 700
});

await this.utils.visitPage(url);

const allImages = await this.page.evaluate((url) => {
const images = document.querySelectorAll('img'),
result = [];

Array.from(images).forEach((img) => {
result.push({
src: img.getAttribute('src'),
url: url,
lazyloaded: img.classList.contains('lazyloaded')
})
});

return result;
}, url);

allImages.forEach((image) => {
if(singlePageLcp.lcp.includes(image.src) && image.lazyloaded ) {
truthy = false;
}

if(singlePageLcp.viewport.includes(image.src) && image.lazyloaded ) {
truthy = false;
}
});

// Fail test when there is expectation mismatch.
expect(truthy).toBeTruthy();
});

/**
* Executes the step to visit page in a specific browser dimension.
*/
When('I visit page {string} and check for lcp', async function (this:ICustomWorld, page) {

const tablePrefix: string = await getWPTablePrefix();

await this.page.setViewportSize({
width: 1600,
height: 700,
});

await this.utils.visitPage(page);

// Wait the beacon to add an attribute `beacon-complete` to true before fetching from DB.
await this.page.waitForFunction(() => {
const beacon = document.querySelector('[data-name="wpr-wpr-beacon"]');
return beacon && beacon.getAttribute('beacon-completed') === 'true';
}, { timeout: 100000 });

// Get the LCP/ATF from the DB
const sql = `SELECT lcp, viewport
FROM ${tablePrefix}wpr_above_the_fold
WHERE url LIKE "%${page}%"
AND is_mobile = 0`;
const result = await dbQuery(sql);
const resultFromStdout = await extractFromStdout(result);

// If no DB result, set assertion var to false, fail msg and skip the loop.
if (!resultFromStdout || resultFromStdout.length === 0) {
isDbResultAvailable = false;
}

singlePageLcp = {
url: page,
lcp: resultFromStdout[0].lcp,
viewport: resultFromStdout[0].viewport
}
});
14 changes: 13 additions & 1 deletion utils/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ export async function activatePlugin(name: string): Promise<void> {
/**
* Check if plugin is installed
* @function
* @name activatePlugin
* @name isPluginInstalled
* @async
* @param {string} name - The name of the plugin to be checked if installed.
* @returns {Promise<boolean>} - A Promise that resolves when the check is completed.
Expand All @@ -248,6 +248,18 @@ export async function isPluginInstalled(name: string): Promise<boolean> {
return await wp(`plugin is-installed ${name}`, false);
}

/**
* Delete a plugin if exist.
* Note: this is not ideal for wpr or imagify plugins as it doesn't delete DB data which relies on uninstall hook.
* @function
* @name deletePlugin
* @async
* @param {string} name - The name of the plugin to be deleted if installed.
* @returns {Promise<boolean>} - A Promise that resolves when the check is completed.
*/
export async function deletePlugin(name: string): Promise<boolean> {
return await wp(`plugin delete ${name}`, false);
Khadreal marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* Install a WordPress plugin from a remote zip file using the WP-CLI command.
Expand Down
9 changes: 9 additions & 0 deletions utils/page-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,15 @@ export class PageUtils {
await this.page.goto(WP_BASE_URL + '/wp-admin/options-general.php?page=wprocket#dashboard');
}

/**
* Navigates to Imagify settings page.
*
* @return {Promise<void>}
*/
public gotoImagify = async (): Promise<void> => {
await this.page.goto(WP_BASE_URL + '/wp-admin/options-general.php?page=imagify');
}

/**
* Navigates to new post on Wordpress.
*
Expand Down
Loading
Loading