Skip to content
Merged
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
53 changes: 53 additions & 0 deletions .github/workflows/copilot-setup-steps.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
name: Copilot Setup Steps

on:
workflow_dispatch:
push:
paths:
- .github/workflows/copilot-setup-steps.yml
pull_request:
paths:
- .github/workflows/copilot-setup-steps.yml

jobs:
copilot-setup-steps:
runs-on: ubuntu-22.04
timeout-minutes: 30
permissions:
contents: read
services:
database:
image: mysql:latest
env:
MYSQL_DATABASE: wordpress_tests
MYSQL_ROOT_PASSWORD: root
ports:
- 3306:3306
options: >-
--health-cmd="mysqladmin ping -h 127.0.0.1 -uroot -proot"
--health-interval=10s
--health-timeout=5s
--health-retries=10
steps:
- name: Checkout source code
uses: actions/checkout@v4

- name: Setup PHP version
uses: shivammathur/setup-php@v2
with:
php-version: '8.0'
extensions: simplexml, mysql
tools: phpunit-polyfills:1.1
coverage: none

- name: Install WordPress test suite prerequisites
run: sudo apt-get update --allow-releaseinfo-change && sudo apt-get install -y subversion default-mysql-client

- name: Install WordPress Test Suite
run: composer install-wp-tests

- name: Install Composer dependencies
run: composer install --prefer-dist --no-progress --no-suggest

- name: Verify PHPUnit is available
run: composer phpunit -- --version
24 changes: 19 additions & 5 deletions .github/workflows/test-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ on:
pull_request:

concurrency:
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.ref }}
cancel-in-progress: true
# Every run deploys to the same QA host, so separate PRs must not overwrite
# one another while their E2E suites are running.
group: e2e-testing-optimole-qa
cancel-in-progress: false

jobs:
e2e:
Expand All @@ -16,13 +18,13 @@ jobs:
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 16
node-version: 22
cache: 'npm'
- name: Install dependencies
run: |
npm ci
composer install --no-dev --prefer-dist --no-progress --no-suggest
npx playwright install
npx playwright install --with-deps chromium firefox
- name: Make build
run: |
npm run build
Expand All @@ -39,9 +41,21 @@ jobs:
run: ./bin/run-e2e-tests.sh
- name: Run E2E tests
id: run_tests
env:
E2E_BASE_URL: http://testing.optimole.com
run: |
npm run e2e:run
continue-on-error: false
- name: Check test results
if: steps.run_tests.outcome != 'success'
run: exit 1
run: exit 1
- name: Upload Playwright artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: playwright-artifacts
path: |
test-results/
playwright-report/
if-no-files-found: warn
retention-days: 7
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,10 @@ const OffloadMedia = ({ settings, canSave, setSettings, setCanSave }) => {
setCheckedOffloadConflicts( false );
setOffloadConflicts([]);

checkOffloadConflicts( response => {
checkOffloadConflicts( async response => {
if ( 0 === response.data.length ) {
await clearOffloadErrors( 'rollback_images' );

const nextSettings = { ...settings };
nextSettings['show_offload_finish_notice'] = '';
nextSettings['rollback_status'] = 'enabled';
Expand Down
4 changes: 2 additions & 2 deletions assets/src/dashboard/utils/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -589,10 +589,10 @@ export const callSync = ( data ) => {
});
};

export const clearOffloadErrors = async() => {
export const clearOffloadErrors = async( action = 'offload_images' ) => {
try {
return await apiFetch({
path: optimoleDashboardApp.routes['clear_offload_errors'],
path: addQueryArgs( optimoleDashboardApp.routes['clear_offload_errors'], { action }),
method: 'GET'
});
} catch ( error ) {
Expand Down
32 changes: 29 additions & 3 deletions bin/run-e2e-tests.sh
Original file line number Diff line number Diff line change
@@ -1,8 +1,34 @@
#!/usr/bin/env bash
set -euo pipefail

readonly DB_RETRY_DELAY=5
readonly DB_MAX_ATTEMPTS=24
readonly QA_URL='http://testing.optimole.com'

eval "$(ssh-agent -s)"
mkdir $HOME/.ssh
echo "$SSH_KEY" > "$HOME/.ssh/key"
install -d -m 700 "$HOME/.ssh"
printf '%s\n' "$SSH_KEY" > "$HOME/.ssh/key"
chmod 600 "$HOME/.ssh/key"

rsync --delete -rc --force --exclude-from="$GITHUB_WORKSPACE/.distignore" -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no -p $SSH_PORT" "$GITHUB_WORKSPACE/dist/optimole-wp/" $SSH_USERNAME@$SSH_HOST:$SSH_PATH
ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no -p $SSH_PORT $SSH_USERNAME@$SSH_HOST "cd /var/www && docker-compose run --rm cli wp elementor flush_css --url=http://testing.optimole.com"
ssh -i "$HOME/.ssh/key" -o StrictHostKeyChecking=no -p "$SSH_PORT" "$SSH_USERNAME@$SSH_HOST" "DB_RETRY_DELAY='$DB_RETRY_DELAY' DB_MAX_ATTEMPTS='$DB_MAX_ATTEMPTS' QA_URL='$QA_URL' bash -s" <<'REMOTE_COMMAND'
set -euo pipefail

cd /var/www
docker-compose up -d db wordpress

attempt=1
until docker-compose run --rm cli wp db check --url="$QA_URL"; do

if [ "$attempt" -ge "$DB_MAX_ATTEMPTS" ]; then
echo "The QA database did not become ready after $DB_MAX_ATTEMPTS attempts." >&2
exit 1
fi

echo "QA database is not ready (attempt $attempt/$DB_MAX_ATTEMPTS); retrying in $DB_RETRY_DELAY seconds..." >&2
attempt=$((attempt + 1))
sleep "$DB_RETRY_DELAY"
done

docker-compose run --rm cli wp elementor flush_css --url="$QA_URL"
REMOTE_COMMAND
3 changes: 3 additions & 0 deletions inc/cli/cli_media.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ private function update_images_template( $action ) {
}

Optml_Media_Offload::clear_offload_errors_meta();
if ( $action === 'rollback' ) {
Optml_Media_Offload::clear_rollback_errors_meta();
}
WP_CLI::line( $strings[ $action ]['info'] );
$number_of_images_for = 'offload_images';
if ( $action === 'rollback' ) {
Expand Down
48 changes: 45 additions & 3 deletions inc/media_offload.php
Original file line number Diff line number Diff line change
Expand Up @@ -2673,15 +2673,57 @@ private function get_offloaded_attachment_url( $attachment_id, $url ) {

/**
* Cleanup the offload errors meta.
*
* @param string $meta_key The meta key to delete. Defaults to the offload error key.
*
* @return int|bool Number of rows affected/selected or false on error.
*/
public static function clear_offload_errors_meta() {
public static function clear_offload_errors_meta( $meta_key = '' ) {
global $wpdb;

return $wpdb->query(
if ( empty( $meta_key ) ) {
$meta_key = self::META_KEYS['offload_error'];
}

// Collect the affected attachments before the bulk delete so their object
// caches can be invalidated. A raw DELETE bypasses the meta/query caches,
// which would otherwise leave stale WP_Query results for subsequent queries.
$post_ids = $wpdb->get_col(
$wpdb->prepare(
"SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key = %s",
$meta_key
)
);

$result = $wpdb->query(
$wpdb->prepare(
"DELETE FROM {$wpdb->postmeta} WHERE meta_key = %s",
self::META_KEYS['offload_error']
$meta_key
)
);

foreach ( $post_ids as $post_id ) {
wp_cache_delete( (int) $post_id, 'post_meta' );
}

// Bump the posts last_changed so cached WP_Query results (which are keyed
// on it) are recomputed on the next query. The raw DELETE above does not
// touch the object cache, so without this the retried rollback/offload
// query could return a stale set that still excludes the cleared posts.
wp_cache_set( 'last_changed', microtime(), 'posts' );

return $result;
}

/**
* Cleanup the rollback errors meta.
*
* Used when the user retries the rollback process so previously errored
* attachments are considered again for restore.
*
* @return int|bool Number of rows affected/selected or false on error.
*/
public static function clear_rollback_errors_meta() {
return self::clear_offload_errors_meta( self::META_KEYS['rollback_error'] );
}
}
8 changes: 7 additions & 1 deletion inc/rest.php
Original file line number Diff line number Diff line change
Expand Up @@ -844,7 +844,13 @@ public function number_of_images_and_pages( WP_REST_Request $request ) {
* @return WP_REST_Response
*/
public function clear_offload_errors( WP_REST_Request $request ) {
$delete_count = Optml_Media_Offload::clear_offload_errors_meta();
$action = $request->get_param( 'action' );

if ( 'rollback_images' === $action ) {
$delete_count = Optml_Media_Offload::clear_rollback_errors_meta();
} else {
$delete_count = Optml_Media_Offload::clear_offload_errors_meta();
}

return $this->response( [ 'success' => $delete_count ] );
}
Expand Down
6 changes: 0 additions & 6 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -2034,12 +2034,6 @@ parameters:
count: 1
path: inc/media_offload.php

-
message: '#^Method Optml_Media_Offload\:\:clear_offload_errors_meta\(\) has no return type specified\.$#'
identifier: missingType.return
count: 1
path: inc/media_offload.php

-
message: '#^Method Optml_Media_Offload\:\:delete_attachment_from_server\(\) has no return type specified\.$#'
identifier: missingType.return
Expand Down
16 changes: 13 additions & 3 deletions playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,22 @@ import { defineConfig } from '@playwright/test';
export default defineConfig({
testDir: './tests/e2e',
timeout: 60000,
workers: process.env.CI ? 2 : 5,
// The CI suite targets one shared QA site, so concurrent browser sessions
// must not race over its lazy-loading state or third-party integrations.
workers: process.env.CI ? 1 : 5,
retries: process.env.CI ? 2 : 0,
reporter: process.env.CI
? [['line'], ['html', { open: 'never', outputFolder: 'playwright-report' }]]
: 'list',
use: {
baseURL: 'http://testing.optimole.com', // Replace with your local WordPress URL
// CI supplies the shared QA URL. Local runs are deliberately local by
// default to avoid exercising the mutable QA site accidentally.
baseURL: process.env.E2E_BASE_URL || 'http://localhost',
trace: 'on-first-retry',
navigationTimeout: 45000,
screenshot: 'only-on-failure',
video: 'retain-on-failure',
viewport: { width: 1280, height: 720 },
},
projects: [
{
Expand All @@ -20,4 +30,4 @@ export default defineConfig({
use: { browserName: 'firefox' },
},
]
});
});
17 changes: 6 additions & 11 deletions tests/e2e/README.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,25 @@
> [!IMPORTANT]
> Tests run on the testing.optimole.com WordPress instance, so running in a local environment might not reflect the actual results.
> CI deploys to and tests a shared QA instance. Local runs default to `http://localhost`; set `E2E_BASE_URL` to your local WordPress URL before running the suite.

### To run e2e tests locally:

In the root directory of the project, run:
```bash
npm install --frozen-lockfile
npm ci
npx playwright install
npm run e2e:open
E2E_BASE_URL=http://localhost:8080 npm run e2e:open
```

```bash
#You can run the tests in headless mode by running:
npm run e2e:run
E2E_BASE_URL=http://localhost:8080 npm run e2e:run

# If you want to run individual tests, you can do so by running:
npm run e2e:test tests/e2e/<test-name>

# You can directly run the tests using playwright by running:
npx playwright test tests/e2e/<test-name>
# Run an individual test file:
E2E_BASE_URL=http://localhost:8080 npx playwright test tests/e2e/<test-name>.spec.ts
```








6 changes: 3 additions & 3 deletions tests/e2e/beaver-builder-background-lazyload.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ test.describe('Check Homepage', () => {
});

test('After scroll the background images should be loaded', async ({ page }) => {
await page.evaluate(() => window.scrollTo(0, 2500));

const column = page.locator('.entry-content .fl-col-content').nth(4);
await column.scrollIntoViewIfNeeded();
await expect(column).toHaveClass(/optml-bg-lazyloaded/);

const row = page.locator('.entry-content .fl-row-bg-photo > .fl-row-content-wrap').nth(1);
await row.scrollIntoViewIfNeeded();
await expect(row).toHaveClass(/optml-bg-lazyloaded/);
});
});
});
7 changes: 4 additions & 3 deletions tests/e2e/divi-background-lazyload.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,16 @@ test.describe('Check Divi Background Page', () => {
});

test('After scroll backgrounds should be loaded', async ({ page }) => {
await page.evaluate(() => window.scrollTo(0, 4250));

const slide = page.locator('.entry-content .et_pb_slides > .et_pb_slide_3').first();
await slide.scrollIntoViewIfNeeded();
await expect(slide).toHaveClass(/optml-bg-lazyloaded/);

const module = page.locator('.entry-content .et_pb_module').nth(4);
await module.scrollIntoViewIfNeeded();
await expect(module).toHaveClass(/optml-bg-lazyloaded/);

const row = page.locator('.entry-content .et_pb_row_3').first();
await row.scrollIntoViewIfNeeded();
await expect(row).toHaveClass(/optml-bg-lazyloaded/);
});
});
});
3 changes: 2 additions & 1 deletion tests/e2e/elementor-background-lazyload.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ test.describe('Check Elementor Background Page', () => {

for (let i = 6; i < 12; i++) {
const item = page.locator('.elementor-inner .elementor-background-slideshow__slide__image').nth(i);
await item.scrollIntoViewIfNeeded();
await expect(item).toHaveClass(/optml-bg-lazyloaded/);
}
});
});
});
Loading
Loading