---
title: >-
  Storefront: Practical Lab – Verifying Campaign Styling With Playwright |
  Shopware Community Hub
description: >-
  Verify campaign-based storefront customizations with small Playwright tests
  that check whether the expected campaign elements are rendered in the listing
  and…
canonical_url: >-
  https://hub.shopware.com/learn/unit/storefront-practical-lab-verifying-campaign-styling-with-playwright
---

# Storefront: Practical Lab – Verifying Campaign Styling With Playwright

<LearningObjectives>

- Add stable test hooks to storefront template customizations.
- Explain why Playwright tests should verify meaningful DOM output instead of fragile styling details.
- Set up a small Playwright project inside a Shopware theme plugin.
- Verify that campaign elements are rendered in the product listing.
- Verify a listing-to-PDP journey for a campaign product.
- Run the Playwright test locally and troubleshoot common setup or selector issues.

</LearningObjectives>

# Storefront: Practical Lab – Verifying Campaign Styling With Playwright

In the previous practical lab, you implemented campaign-based storefront styling. Products marked with the custom field `academy_product_black_friday` received dedicated markup and styling in the listing and on the product detail page (PDP).

Now you will verify the result with small Playwright tests.

<Callout title="Demo Data" type="info">

This practical lab uses local Shopware demo data. Demo data can differ between local setups and may also differ from the exact data you used in the previous practical lab.

The test logic stays the same, but the concrete category URL or campaign product may need to be adjusted in your local test file.

</Callout>

The goal is not to test every CSS detail. Colors, spacing, and exact visual output can change for valid design reasons. Instead, this lab focuses on logical checks:

- Is the campaign product card rendered in the listing?
- Is the campaign buy box rendered on the PDP?
- Are the expected campaign-specific DOM hooks available?

This gives you a lightweight safety net for the template customizations you created in the previous lab. The tests do not replace manual visual review, but they help you detect when important campaign elements are no longer rendered.

<Callout title="Connection to Frontend Development Essentials" type="info">

Playwright was introduced in the previous Frontend Development Essentials learning path. This learning unit does not teach Playwright from scratch again. Instead, you will apply it to a real storefront customization from this learning path.

</Callout>

## Target Result

By the end of this practical lab, you will have:

- Added stable test attributes to your campaign-related storefront markup.
- Created a small Playwright project and test file for the campaign styling lab.
- Verified that the listing contains a campaign product card.
- Verified a listing-to-PDP journey for a campaign product.
- Run the test locally and use the result as feedback for your storefront implementation.

Your test result should look similar to this:

![Playwright Test Result](assets/images/playwright-ui-listing-passed.jpg)

## Testing Strategy

In storefront customization tests, avoid testing implementation details that are likely to change.

For this lab, do **not** test:

- exact CSS colors,
- exact spacing,
- exact Bootstrap class combinations,
- pixel-perfect visual output.

Instead, test the behavior that matters for this customization:

- A product with the campaign custom field renders the campaign markup.
- The campaign-specific wrapper exists in the listing.
- The campaign-specific wrapper exists on the PDP.

The mental model is: Playwright verifies that your customization reaches the browser. SCSS still controls how it looks.

## Install Playwright in Your Local Shopware

First, add a Playwright test setup to your local Shopware project.

Navigate from your Shopware root directory to the theme plugin from the previous practical lab:

```bash
cd custom/plugins/FrontendDevIntermediateCampaignStylingTheme
```

Create a dedicated folder for the Playwright test setup. In Shopware plugins, acceptance tests are commonly placed under `tests/acceptance`:

```bash
mkdir -p tests/acceptance
```

Then navigate to this folder:

```bash
cd tests/acceptance
```

Now initialize Playwright in this folder:

```bash
npm init playwright@latest
```

This command starts Playwright's project initializer in the current folder. It does not install Playwright globally and it does not change your Shopware application code. Instead, it prepares a small Node-based test project inside `tests/acceptance`.

During the setup, choose the following options:

```bash
Need to install the following packages:
create-playwright@1.17.139
Ok to proceed? (y) y


> npx
> create-playwright

Getting started with writing end-to-end tests with Playwright:
Initializing project in '.'
✔ Do you want to use TypeScript or JavaScript? · JavaScript # JavaScript was chosen
✔ Where to put your end-to-end tests? · tests # tests folder will be created
✔ Add a GitHub Actions workflow? (Y/n) · false # No GitHub Actions
✔ Install Playwright browsers (can be done manually via 'npx playwright install')? (Y/n) · true # Install Playwright for browsers
✔ Install Playwright operating system dependencies (requires sudo / root - can be done manually via 'sudo npx playwright install-deps')? (y/N) · true # Install system dependencies
```

This prepares the Playwright test setup inside your theme plugin. It creates the `package.json`, `package-lock.json`, `playwright.config.js`, the `tests/` folder, and the local `node_modules/` folder.

At this state, your theme should look like this:

```text
[shop_root]
 └─ custom/plugins
    └─ FrontendDevIntermediateCampaignStylingTheme
        ├─ src
        │  ├─ Lifecycle
        │  │  └─ CustomFieldsLifecycle.php
        │  ├─ Resources
        │  │  ├─ theme.json
        │  │  ├─ app/storefront/src/scss/...
        │  │  ├─ snippet/...
        │  │  └─ views/storefront/...
        │  └─ FrontendDevIntermediateCampaignStylingTheme.php
        ├─ tests
        │  └─ acceptance
        │     ├─ node_modules/
        │     ├─ tests/
        │     │  └─ example.spec.js
        │     ├─ package.json
        │     ├─ package-lock.json
        │     └─ playwright.config.js
        └─ composer.json
```

<Callout title="Run Commands From tests/acceptance" type="info">

The Playwright project lives in `tests/acceptance`. Run Playwright commands from this folder, because this is where the `package.json`, `node_modules`, and `playwright.config.js` files are located.

</Callout>

<Callout title="Local Browser Dependencies" type="info">

Depending on your local shop setup, Playwright browser and system dependency handling can differ. Docker-based setups, local Linux setups, WSL setups, and managed development environments may require different configuration.

Use the setup that fits your local environment. The important point for this lab is that Playwright can start a browser and open your local storefront URL.

</Callout>

### Configure the Storefront URL

Playwright needs to know which local storefront it should open when your test uses `page.goto()`.

Configure `baseURL` in `playwright.config.js` so it points to your local storefront. If your local environment exposes `APP_URL`, you can use it like this:

```js
// ...
use: {
  baseURL: process.env.APP_URL,
  trace: 'on-first-retry',
},
// ...
```

With this configuration, this test command:

```js
await page.goto('/Free-time-electronics/');
```

Opens this page:

```text
http://localhost:8000/Free-time-electronics/
```

<Callout title="Local Environment" type="info">

In this practical lab, the local shop runs under `http://localhost:8000`. If your shop uses another URL, adjust your local environment variable or set the `baseURL` to the URL of your local storefront.

</Callout>

## Test the Listing

Now you can write the first real e2e test for the campaign listing.

The goal of this test is not to check colors or pixel-perfect styling. The goal is to verify that the campaign product card is rendered and that the adapted campaign price structure exists inside that card.

### Step 1: Add Test Hooks to the Listing Markup

First, add stable test hooks to the listing-related Twig templates.

In `src/Resources/views/storefront/component/product/card/box-standard.html.twig`, add `data-testid="campaign-product-card"` to the campaign wrapper:

```twig
{% sw_extends '@Storefront/storefront/component/product/card/box-standard.html.twig' %}

{% block component_product_box %}
    {% if product.translated.customFields.academy_product_black_friday is not same as(true) %}
        {{ parent() }}
    {% else %}
        <div class="academy-product-box" data-testid="campaign-product-card">
            {{ parent() }}
        </div>
    {% endif %}
{% endblock %}
```

In `src/Resources/views/storefront/component/product/card/price-unit.html.twig`, add `data-testid="campaign-product-price"` to the adapted price wrapper:

```twig
{% sw_extends '@Storefront/storefront/component/product/card/price-unit.html.twig' %}

{% block component_product_box_main_price %}
    {% if product.translated.customFields.academy_product_black_friday is not same as(true) %}
        {{ parent() }}
    {% else %}
        <div class="academy-black-friday-price-wrapper d-inline-flex align-self-stretch flex-wrap" data-testid="campaign-product-price">
            {# Existing campaign price markup #}
        </div>
    {% endif %}
{% endblock %}
```

The CSS classes still belong to styling. The `data-testid` attributes are only for the test. Playwright provides the `getByTestId()` locator for this pattern and, by default, it looks for `data-testid` attributes in the rendered HTML.

This separation keeps the Playwright test stable even if you later rename or refactor styling classes. Styling classes can change with the design. Test hooks should describe the feature or UI element that must stay findable for the test.

After changing Twig templates, you might need to clear the cache:

```bash
bin/console cache:clear
```

### Step 2: Write the Listing Test

Rename the generated example file to something meaningful:

```bash
mv tests/example.spec.js tests/campaign-styling.spec.js
```

After renaming the file, your test folder should look like this:

```text
[shop_root]
 └─ custom/plugins
    └─ FrontendDevIntermediateCampaignStylingTheme
        ├─ src
        │  ├─ Lifecycle
        │  │  └─ CustomFieldsLifecycle.php
        │  ├─ Resources
        │  │  ├─ theme.json
        │  │  ├─ app/storefront/src/scss/...
        │  │  ├─ snippet/...
        │  │  └─ views/storefront/...
        │  └─ FrontendDevIntermediateCampaignStylingTheme.php
        ├─ tests
        │  └─ acceptance
        │     ├─ node_modules/
        │     ├─ tests/
        │     │  └─ campaign-styling.spec.js // <-- Renamed
        │     ├─ package.json
        │     ├─ package-lock.json
        │     └─ playwright.config.js
        └─ composer.json
```

Then add the following test in the `campaign-styling.spec.js` file:

```js
import { test, expect } from '@playwright/test';

test.describe('Campaign Styling: Listing', () => {
  test('shows a campaign product card with campaign price styling in the listing', async ({ page }) => {
    await page.goto('/Free-time-electronics/');

    const campaignProductCard = page.getByTestId('campaign-product-card').first();
    const campaignProductPrice = campaignProductCard.getByTestId('campaign-product-price');

    await expect(campaignProductCard).toBeVisible();
    await expect(campaignProductPrice).toBeVisible();
  });
});
```

This test does three things:

- It opens the category page that contains the campaign product.
- It finds the first campaign product card by its stable `data-testid`.
- It verifies that the adapted campaign price area exists inside that product card.

<Callout title="Listing URL" type="info">

This practical lab uses Shopware demo data. In this local setup, the campaign product is visible in `/Free-time-electronics/`. If your campaign product is assigned to another category, replace the value in `page.goto()` with your local category URL.

</Callout>

### Step 3: Run the Listing Test

Run the Playwright UI from your Playwright project folder:

```bash
npx playwright test --ui
```

This opens the Playwright UI.

![Playwright UI](assets/images/playwright-ui.jpg)

Click **Run all** to run the test in the selected browser.

If the test succeeds, the result should look similar to this:

![Playwright UI: Successful Listing Test](assets/images/playwright-ui-listing-passed.jpg)

## Test the PDP

Next, verify the product detail page.

This test follows a small customer journey:

1. The customer opens the campaign category.
2. The customer clicks the campaign product card.
3. The customer lands on the PDP.
4. The PDP renders the campaign buy box and adapted list price area.

### Step 1: Add Test Hooks to the PDP Markup

In `src/Resources/views/storefront/block/cms-block-gallery-buybox.html.twig`, add `data-testid="campaign-product-detail-buy"` to the campaign buy box wrapper:

```twig
<div class="academy-product-detail-buy col-lg-5 product-detail-buy"
     data-testid="campaign-product-detail-buy"
     data-cms-element-id="{{ element.id }}">
    {% block block_gallery_buybox_column_right_inner %}
        {% sw_include '@Storefront/storefront/element/cms-element-' ~ element.type ~ '.html.twig' ignore missing %}
    {% endblock %}
</div>
```

In `src/Resources/views/storefront/component/buy-widget/buy-widget-price.html.twig`, add `data-testid="campaign-product-detail-list-price"` to the adapted list price wrapper:

```twig
<div class="col-12 p-0 product-detail-list-price-wrapper" data-testid="campaign-product-detail-list-price">
    <span class="list-price-percentage fw-bold">-{{ listPrice.percentage|round(0, 'floor') }}%</span>
    {# Existing campaign list price markup #}
</div>
```

Again, you might need to clear the cache:

```bash
bin/console cache:clear
```

### Step 2: Write the PDP Test

Add the PDP test below the listing test:

```js
test.describe('Campaign Styling: PDP', () => {
  test('shows the campaign buy box with adapted list price styling on the PDP', async ({ page }) => {
    await page.goto('/Free-time-electronics/');

    const campaignProductCard = page.getByTestId('campaign-product-card').first();
    await expect(campaignProductCard).toBeVisible();

    const productLink = campaignProductCard.locator('a.product-image-link, a.product-name').first();
    await expect(productLink).toBeVisible();
    await productLink.click();

    const campaignDetailBuyBox = page.getByTestId('campaign-product-detail-buy');
    const campaignDetailListPrice = campaignDetailBuyBox.getByTestId('campaign-product-detail-list-price');

    await expect(campaignDetailBuyBox).toBeVisible();
    await expect(campaignDetailListPrice).toBeVisible();
  });
});
```

This test verifies the full journey from listing to PDP. It does not open a hardcoded product URL. Instead, it starts with the campaign product card from the listing and follows the link to the product detail page.

### Step 3: Run the PDP Test

If the Playwright UI is already open, press the reload button. The PDP test should appear next to the listing test.

![Playwright UI: Reload Button](assets/images/playwright-ui-reload-button.jpg)

![Playwright UI: Reloaded](assets/images/playwright-ui-reloaded.jpg)

Run the tests again. If the test succeeds, the result should look similar to this:

![Playwright UI: Successful PDP Test](assets/images/playwright-ui-pdp-passed.jpg)

## Double-Check With the Plugin

If you want to compare your implementation with the reference solution, you can use our Academy [repository](https://github.com/ShopwareAcademy/FrontendDevIntermediateCampaignStylingTheme/tree/frontend-dev-intermediate-campaign-style-playwright).

Or clone it locally:

```bash
git clone git@github.com:ShopwareAcademy/FrontendDevIntermediateCampaignStylingTheme.git
```

And then checkout the related Git tag:

```bash
cd FrontendDevIntermediateCampaignStylingTheme
git checkout tags/frontend-dev-intermediate-campaign-style-playwright
```

<Callout title="Adjust the Listing URL" type="info">

The reference solution uses the demo data. If your local demo data differs, the campaign product may be assigned to another category.

In that case, open your storefront, find a category that contains a product with `academy_product_black_friday` enabled, and adjust the URL in `tests/campaign-styling.spec.js`:

```js
await page.goto('/your-category-url/');
```

</Callout>

## Interpret Failures

If the test fails, use the failure as a debugging hint.

### The Listing Selector Is Not Found

Check these points:

- The product with `academy_product_black_friday` is visible on the page opened by the test.
- The listing template contains `data-testid="campaign-product-card"`.
- The listing price template contains `data-testid="campaign-product-price"`.
- The custom field is enabled for at least one visible product.
- The theme was compiled after changing the Twig template.
- The cache was cleared after the template change.

### The PDP Selector Is Not Found

Check these points:

- The test can find and click the product link inside the campaign product card.
- The PDP template contains `data-testid="campaign-product-detail-buy"`.
- The PDP price template contains `data-testid="campaign-product-detail-list-price"`.
- The product detail page uses the expected buy box template.
- The theme assigned to the sales channel is the theme from the previous lab.

### The Storefront URL Does Not Load

Check these points:

- `baseURL` in `playwright.config.js` points to your local storefront.
- If you use `process.env.APP_URL`, the variable is available in the shell where you run Playwright.
- Your local Shopware instance is running.
- The storefront is reachable in the browser with the same URL.

### Failed Tests

Now trigger a failure intentionally.

Imagine that someone changes the campaign product card template and accidentally removes an important part of the HTML. For example, in `box-standard.html.twig`, the campaign wrapper no longer contains `data-testid="campaign-product-card"`.

When you run the tests again, Playwright should show a failed result:

![Playwright UI: Tests Failed](assets/images/playwright-ui-tests-failed.jpg)

The listing test fails because it can no longer find the campaign product card by its stable test hook.

The PDP test can fail as a consequence as well. This is expected in this case because the PDP test starts with the campaign product card in the listing. If the test cannot find that card, it cannot click the product link and continue to the PDP.

This is useful feedback. It shows that the tests cover a logical chain:

- The listing must render the campaign card.
- The campaign card must contain a clickable product link.
- The PDP must render the campaign buy box and adapted list price area.

If one part of that chain breaks, Playwright points you to the broken step before the change reaches users or a CI pipeline.

This is the value of an E2E test: It does not only check isolated implementation details. It verifies whether the important parts of a customer journey still work in the browser.

However, this does not mean that every test should depend on many previous steps. Keep the chain as short as possible and only connect steps that belong to the same meaningful customer journey. In this lab, the PDP test intentionally starts in the listing because the campaign customization affects both places and the customer reaches the PDP through the campaign product card.

If you only wanted to verify the PDP markup in isolation, opening a fixed product detail URL would also be valid. For this lab, the journey-based test is useful because it verifies the campaign flow from listing to PDP.

## Summary

In this practical lab, you extended the campaign customization from the previous lab with a small Playwright test suite.

You learned how to:

- Add stable test hooks to storefront template customizations.
- Verify campaign-specific DOM output in the listing.
- Verify a listing-to-PDP journey for a campaign product.
- Keep Playwright tests focused on logical storefront behavior instead of fragile styling details.

With this approach, your campaign customization is not only checked manually. You now have a small automated safety net for the most important storefront output.
