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
111 changes: 111 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -164,3 +164,114 @@ jobs:
- run: npm run build:storage:lite
- run: npm run test:sync
- run: npm run test:sync:schematics

test-visual:
name: Test visual regression

runs-on: ubuntu-latest

steps:

- uses: actions/checkout@v3

- uses: actions/setup-node@v3
with:
node-version: 24

# Detecta o nome da branch atual
- name: Get branch name
id: branch
env:
HEAD_REF: ${{ github.head_ref }}
REF_NAME: ${{ github.ref_name }}
EVENT_NAME: ${{ github.event_name }}
run: |
if [ "$EVENT_NAME" = "pull_request" ]; then
BRANCH_NAME="$HEAD_REF"
else
BRANCH_NAME="$REF_NAME"
fi
echo "name=$BRANCH_NAME" >> $GITHUB_OUTPUT
echo "Branch name: $BRANCH_NAME"

# Verifica se existe uma branch de mesmo nome no po-style
- name: Check for matching po-style branch
id: po_style
env:
BRANCH_NAME: ${{ steps.branch.outputs.name }}
run: |
ENCODED_BRANCH=$(echo "$BRANCH_NAME" | sed 's|/|%2F|g')
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
"https://api.github.com/repos/po-ui/po-style/branches/$ENCODED_BRANCH")
if [ "$HTTP_STATUS" = "200" ]; then
echo "found=true" >> $GITHUB_OUTPUT
echo "Branch '$BRANCH_NAME' encontrada no po-style"
else
echo "found=false" >> $GITHUB_OUTPUT
echo "Branch '$BRANCH_NAME' nao encontrada no po-style (HTTP $HTTP_STATUS), usando @po-ui/style do npm"
fi

# Se encontrou a branch, clona po-style, builda e gera o tgz
- name: Build po-style from matching branch
if: steps.po_style.outputs.found == 'true'
env:
BRANCH_NAME: ${{ steps.branch.outputs.name }}
run: |
git clone --branch "$BRANCH_NAME" --depth 1 https://github.com/po-ui/po-style.git /tmp/po-style
cd /tmp/po-style
npm ci
npm run build
npm run pack:style
echo "TGZ gerado:"
ls -la dist/style/*.tgz

# Instala dependencias do po-angular
- run: npm i

# Se tiver o tgz do po-style, instala por cima da versao do npm
- name: Install po-style from tgz
if: steps.po_style.outputs.found == 'true'
run: |
TGZ_PATH=$(ls /tmp/po-style/dist/style/*.tgz)
echo "Instalando @po-ui/style a partir de: $TGZ_PATH"
npm install "$TGZ_PATH"

- run: npm run build:ui:lite
- run: npx playwright install --with-deps chromium

# Restaura baselines do cache (geradas em execucoes anteriores no CI)
- name: Restore visual regression baselines from cache
id: baselines-cache
uses: actions/cache@v4
with:
path: e2e/visual/__snapshots__
key: visual-baselines-${{ runner.os }}-${{ hashFiles('e2e/visual/**/*.visual.spec.ts', 'e2e/visual/**/*.component.html') }}
restore-keys: |
visual-baselines-${{ runner.os }}-

# Se nao encontrou baselines no cache, gera novas para o ambiente CI
- name: Generate baselines for CI environment
if: steps.baselines-cache.outputs.cache-hit != 'true'
run: npx playwright test --update-snapshots --reporter=list
Comment on lines +253 to +255

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 CI baseline regeneration overwrites committed baselines, making visual regression tests always pass on cache miss

When cache-hit != 'true' (which happens whenever spec/HTML files change, when the cache is evicted, or on first runs for new branches), the CI runs npx playwright test --update-snapshots which regenerates all 186 committed baseline screenshots from the current code. The subsequent npx playwright test then compares against those just-generated baselines, which trivially always passes.

This completely defeats the purpose of visual regression testing in the exact scenario where it matters most: when a developer modifies UI components AND updates test files in the same PR. The --update-snapshots flag updates ALL snapshots (not just missing ones), so even existing tests that should catch regressions in unchanged components get their baselines silently overwritten.

Additionally, the restore-keys fallback (visual-baselines-${{ runner.os }}-) provides no benefit—actions/cache@v4 sets cache-hit to false for restore-key matches, so the restored baselines are immediately overwritten by the regeneration step.

The baselines are committed to the repo but ignored by CI

There are 186 baseline PNGs committed in e2e/visual/__snapshots__/ which represent the approved visual state. The checkout step (actions/checkout@v3) makes them available, but the regeneration step at line 255 overwrites them. The fix should check if committed baselines already exist before regenerating, e.g.:

- name: Generate baselines for CI environment
  if: steps.baselines-cache.outputs.cache-hit != 'true'
  run: |
    if find e2e/visual/__snapshots__ -name '*.png' | grep -q .; then
      echo "Baselines found from repo/cache, skipping regeneration"
    else
      npx playwright test --update-snapshots --reporter=list
    fi
Suggested change
- name: Generate baselines for CI environment
if: steps.baselines-cache.outputs.cache-hit != 'true'
run: npx playwright test --update-snapshots --reporter=list
- name: Generate baselines for CI environment
if: steps.baselines-cache.outputs.cache-hit != 'true'
run: |
if find e2e/visual/__snapshots__ -name '*.png' | grep -q .; then
echo "Baselines found from repo or restore-keys, skipping regeneration"
else
echo "No baselines found, generating new ones"
npx playwright test --update-snapshots --reporter=list
fi
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

O ponto é válido, mas intencional neste caso. As baselines commitadas no repo são geradas em ambiente local (para desenvolvimento) e possuem dimensões ligeiramente diferentes do ambiente CI (GitHub Actions Ubuntu 24.04) — por exemplo, o po-textarea renderiza com 7px de diferença na altura entre os dois ambientes. Se usássemos as baselines commitadas diretamente no CI, os testes sempre falhariam por mismatch de dimensão.

O fluxo desenhado é:

  1. Primeiro run (cache miss): gera baselines no ambiente CI → passa
  2. Runs subsequentes (cache hit): compara contra baselines do cache do CI → detecta regressões entre execuções no CI

Quando os arquivos de spec/HTML mudam, o cache key muda e baselines são regeneradas — isso é esperado, pois os testes mudaram.

Para detecção de regressão cross-versão mais robusta (comparando contra baselines de uma branch base), seria necessário um approach mais sofisticado como Chromatic ou cache baseado na branch target. Isso pode ser evoluído em iterações futuras.


# Executa os testes visuais comparando com as baselines
- name: Run visual regression tests
run: npx playwright test

# Disponibiliza o relatorio HTML do Playwright como artefato
- name: Upload visual regression report
uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: visual-regression-report
path: e2e/visual/playwright-report/
retention-days: 30

# Disponibiliza os resultados de teste (diffs, screenshots) como artefato
- name: Upload visual regression test results
uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: visual-regression-test-results
path: e2e/visual/test-results/
retention-days: 30
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ testem.log
.scannerwork
.env

# Playwright
/e2e/visual/test-results/
/e2e/visual/playwright-report/

# System Files
.DS_Store
Thumbs.db
Expand Down
40 changes: 40 additions & 0 deletions angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,46 @@
}
}
},
"visual-app": {
"projectType": "application",
"schematics": {},
"root": "e2e/visual",
"sourceRoot": "e2e/visual",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:application",
"options": {
"outputPath": {
"base": "dist/visual-app"
},
"index": "e2e/visual/app/index.html",
"polyfills": ["e2e/visual/app/polyfills.ts"],
"tsConfig": "e2e/visual/tsconfig.visual.json",
"assets": [
{
"glob": "**/*",
"input": "node_modules/@po-ui/style/images",
"output": "/assets/images"
}
],
"styles": ["./node_modules/@po-ui/style/css/po-theme-default.min.css"],
"scripts": [],
"extractLicenses": false,
"sourceMap": true,
"optimization": false,
"namedChunks": true,
"browser": "e2e/visual/app/main.ts"
}
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"buildTarget": "visual-app:build"
}
}
}
},
"portal": {
"projectType": "application",
"schematics": {},
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions e2e/visual/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Component } from '@angular/core';

@Component({
selector: 'app-root',
template: '<router-outlet></router-outlet>',
standalone: false
})
export class AppComponent {}
135 changes: 135 additions & 0 deletions e2e/visual/app/app.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

import { PoModule } from '@po-ui/ng-components';

import { AppComponent } from './app.component';

// Sample components - Buttons & Controls
import { SamplePoButtonBasicComponent } from '../../../projects/ui/src/lib/components/po-button/samples/sample-po-button-basic/sample-po-button-basic.component';
import { SamplePoButtonGroupBasicComponent } from '../../../projects/ui/src/lib/components/po-button-group/samples/sample-po-button-group-basic/sample-po-button-group-basic.component';

// Sample components - Form Fields
import { SamplePoInputBasicComponent } from '../../../projects/ui/src/lib/components/po-field/po-input/samples/sample-po-input-basic/sample-po-input-basic.component';
import { SamplePoCheckboxBasicComponent } from '../../../projects/ui/src/lib/components/po-field/po-checkbox/samples/sample-po-checkbox-basic/sample-po-checkbox-basic.component';
import { SamplePoSwitchBasicComponent } from '../../../projects/ui/src/lib/components/po-field/po-switch/samples/sample-po-switch-basic/sample-po-switch-basic.component';
import { SamplePoSelectBasicComponent } from '../../../projects/ui/src/lib/components/po-field/po-select/samples/sample-po-select-basic/sample-po-select-basic.component';

// Sample components - Data Display
import { SamplePoTableBasicComponent } from '../../../projects/ui/src/lib/components/po-table/samples/sample-po-table-basic/sample-po-table-basic.component';
import { SamplePoTagBasicComponent } from '../../../projects/ui/src/lib/components/po-tag/samples/sample-po-tag-basic/sample-po-tag-basic.component';

// Sample components - Layout & Navigation
import { SamplePoAccordionBasicComponent } from '../../../projects/ui/src/lib/components/po-accordion/samples/sample-po-accordion-basic/sample-po-accordion-basic.component';
import { SamplePoDividerBasicComponent } from '../../../projects/ui/src/lib/components/po-divider/samples/sample-po-divider-basic/sample-po-divider-basic.component';
import { SamplePoProgressBasicComponent } from '../../../projects/ui/src/lib/components/po-progress/samples/sample-po-progress-basic/sample-po-progress-basic.component';

// Visual test - po-input states (legacy route, kept for backward compatibility)
import { VisualTestPoInputStatesComponent } from '../po-input/po-input-states.component';

// Visual test - Field state components (co-located in e2e/visual/fields/)
import { VisualTestPoInputFieldStatesComponent } from '../fields/po-input/po-input-states.component';
import { VisualTestPoDecimalStatesComponent } from '../fields/po-decimal/po-decimal-states.component';
import { VisualTestPoEmailStatesComponent } from '../fields/po-email/po-email-states.component';
import { VisualTestPoNumberStatesComponent } from '../fields/po-number/po-number-states.component';
import { VisualTestPoPasswordStatesComponent } from '../fields/po-password/po-password-states.component';
import { VisualTestPoUrlStatesComponent } from '../fields/po-url/po-url-states.component';
import { VisualTestPoLoginStatesComponent } from '../fields/po-login/po-login-states.component';
import { VisualTestPoComboStatesComponent } from '../fields/po-combo/po-combo-states.component';
import { VisualTestPoDatepickerStatesComponent } from '../fields/po-datepicker/po-datepicker-states.component';
import { VisualTestPoDatepickerRangeStatesComponent } from '../fields/po-datepicker-range/po-datepicker-range-states.component';
import { VisualTestPoLookupStatesComponent } from '../fields/po-lookup/po-lookup-states.component';
import { VisualTestPoMultiselectStatesComponent } from '../fields/po-multiselect/po-multiselect-states.component';
import { VisualTestPoSelectStatesComponent } from '../fields/po-select/po-select-states.component';
import { VisualTestPoTextareaStatesComponent } from '../fields/po-textarea/po-textarea-states.component';
import { VisualTestPoRichTextStatesComponent } from '../fields/po-rich-text/po-rich-text-states.component';
import { VisualTestPoUploadStatesComponent } from '../fields/po-upload/po-upload-states.component';
import { VisualTestPoCheckboxStatesComponent } from '../fields/po-checkbox/po-checkbox-states.component';
import { VisualTestPoCheckboxGroupStatesComponent } from '../fields/po-checkbox-group/po-checkbox-group-states.component';
import { VisualTestPoSwitchStatesComponent } from '../fields/po-switch/po-switch-states.component';
import { VisualTestPoRadioGroupStatesComponent } from '../fields/po-radio-group/po-radio-group-states.component';

const visualRoutes: Routes = [
// Basic sample routes
{ path: 'visual/po-button-basic', component: SamplePoButtonBasicComponent },
{ path: 'visual/po-button-group-basic', component: SamplePoButtonGroupBasicComponent },
{ path: 'visual/po-input-basic', component: SamplePoInputBasicComponent },
{ path: 'visual/po-checkbox-basic', component: SamplePoCheckboxBasicComponent },
{ path: 'visual/po-switch-basic', component: SamplePoSwitchBasicComponent },
{ path: 'visual/po-select-basic', component: SamplePoSelectBasicComponent },
{ path: 'visual/po-table-basic', component: SamplePoTableBasicComponent },
{ path: 'visual/po-tag-basic', component: SamplePoTagBasicComponent },
{ path: 'visual/po-accordion-basic', component: SamplePoAccordionBasicComponent },
{ path: 'visual/po-divider-basic', component: SamplePoDividerBasicComponent },
{ path: 'visual/po-progress-basic', component: SamplePoProgressBasicComponent },
{ path: 'visual/po-input-states', component: VisualTestPoInputStatesComponent },

// Field state combination routes (e2e/visual/fields/)
{ path: 'visual/fields/po-input-states', component: VisualTestPoInputFieldStatesComponent },
{ path: 'visual/fields/po-decimal-states', component: VisualTestPoDecimalStatesComponent },
{ path: 'visual/fields/po-email-states', component: VisualTestPoEmailStatesComponent },
{ path: 'visual/fields/po-number-states', component: VisualTestPoNumberStatesComponent },
{ path: 'visual/fields/po-password-states', component: VisualTestPoPasswordStatesComponent },
{ path: 'visual/fields/po-url-states', component: VisualTestPoUrlStatesComponent },
{ path: 'visual/fields/po-login-states', component: VisualTestPoLoginStatesComponent },
{ path: 'visual/fields/po-combo-states', component: VisualTestPoComboStatesComponent },
{ path: 'visual/fields/po-datepicker-states', component: VisualTestPoDatepickerStatesComponent },
{ path: 'visual/fields/po-datepicker-range-states', component: VisualTestPoDatepickerRangeStatesComponent },
{ path: 'visual/fields/po-lookup-states', component: VisualTestPoLookupStatesComponent },
{ path: 'visual/fields/po-multiselect-states', component: VisualTestPoMultiselectStatesComponent },
{ path: 'visual/fields/po-select-states', component: VisualTestPoSelectStatesComponent },
{ path: 'visual/fields/po-textarea-states', component: VisualTestPoTextareaStatesComponent },
{ path: 'visual/fields/po-rich-text-states', component: VisualTestPoRichTextStatesComponent },
{ path: 'visual/fields/po-upload-states', component: VisualTestPoUploadStatesComponent },
{ path: 'visual/fields/po-checkbox-states', component: VisualTestPoCheckboxStatesComponent },
{ path: 'visual/fields/po-checkbox-group-states', component: VisualTestPoCheckboxGroupStatesComponent },
{ path: 'visual/fields/po-switch-states', component: VisualTestPoSwitchStatesComponent },
{ path: 'visual/fields/po-radio-group-states', component: VisualTestPoRadioGroupStatesComponent }
];

@NgModule({
declarations: [
AppComponent,
// Basic samples
SamplePoButtonBasicComponent,
SamplePoButtonGroupBasicComponent,
SamplePoInputBasicComponent,
SamplePoCheckboxBasicComponent,
SamplePoSwitchBasicComponent,
SamplePoSelectBasicComponent,
SamplePoTableBasicComponent,
SamplePoTagBasicComponent,
SamplePoAccordionBasicComponent,
SamplePoDividerBasicComponent,
SamplePoProgressBasicComponent,
VisualTestPoInputStatesComponent,
// Field state components
VisualTestPoInputFieldStatesComponent,
VisualTestPoDecimalStatesComponent,
VisualTestPoEmailStatesComponent,
VisualTestPoNumberStatesComponent,
VisualTestPoPasswordStatesComponent,
VisualTestPoUrlStatesComponent,
VisualTestPoLoginStatesComponent,
VisualTestPoComboStatesComponent,
VisualTestPoDatepickerStatesComponent,
VisualTestPoDatepickerRangeStatesComponent,
VisualTestPoLookupStatesComponent,
VisualTestPoMultiselectStatesComponent,
VisualTestPoSelectStatesComponent,
VisualTestPoTextareaStatesComponent,
VisualTestPoRichTextStatesComponent,
VisualTestPoUploadStatesComponent,
VisualTestPoCheckboxStatesComponent,
VisualTestPoCheckboxGroupStatesComponent,
VisualTestPoSwitchStatesComponent,
VisualTestPoRadioGroupStatesComponent
],
bootstrap: [AppComponent],
imports: [BrowserModule, FormsModule, RouterModule.forRoot(visualRoutes, {}), PoModule],
providers: [provideHttpClient(withInterceptorsFromDi())]
})
export class VisualAppModule {}
12 changes: 12 additions & 0 deletions e2e/visual/app/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>PO UI Visual Regression Tests</title>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<app-root></app-root>
</body>
</html>
8 changes: 8 additions & 0 deletions e2e/visual/app/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { enableProdMode, provideZoneChangeDetection } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { VisualAppModule } from './app.module';

platformBrowserDynamic()
.bootstrapModule(VisualAppModule, { applicationProviders: [provideZoneChangeDetection()] })
.catch(err => console.error('Error in visual-app bootstrap:', err));
1 change: 1 addition & 0 deletions e2e/visual/app/polyfills.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import 'zone.js';
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<div style="padding: 16px; display: flex; flex-direction: column; gap: 24px">
<!-- State: basic -->
<div data-testid="state-basic">
<po-checkbox-group name="basic" p-label="Grupo basico" [p-options]="options"></po-checkbox-group>
</div>

<!-- State: label + helper -->
<div data-testid="state-label-helper">
<po-checkbox-group
name="label-helper"
p-label="Grupo com helper"
p-helper="Texto de ajuda do helper"
[p-options]="options"
></po-checkbox-group>
</div>

<!-- State: label + help-text -->
<div data-testid="state-label-help">
<po-checkbox-group
name="label-help"
p-label="Grupo com help"
p-help="Texto de apoio do campo"
[p-options]="options"
></po-checkbox-group>
</div>

<!-- State: disabled -->
<div data-testid="state-disabled">
<po-checkbox-group
name="disabled"
p-label="Grupo desabilitado"
p-disabled
[p-options]="options"
></po-checkbox-group>
</div>

<!-- State: required -->
<div data-testid="state-required">
<po-checkbox-group name="required" p-label="Grupo obrigatorio" p-required [p-options]="options"></po-checkbox-group>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Component } from '@angular/core';

@Component({
selector: 'app-visual-test-po-checkbox-group-states',
templateUrl: './po-checkbox-group-states.component.html',
standalone: false
})
export class VisualTestPoCheckboxGroupStatesComponent {
options = [
{ label: 'Opcao 1', value: '1' },
{ label: 'Opcao 2', value: '2' },
{ label: 'Opcao 3', value: '3' }
];
}
Loading
Loading