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
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -428,3 +428,8 @@ console-build-arm64: generate-dockerfile-console-plugin console-multiarch-manife
console-push: ## Uploads the container to quay.io/validatedpatterns/${CONSOLE_PLUGIN_IMAGE}
@echo "Uploading the ${REGISTRY}/${CONSOLE_PLUGIN_IMAGE} container to ${UPLOADREGISTRY}/${CONSOLE_PLUGIN_IMAGE}"
buildah manifest push --all "${REGISTRY}/${CONSOLE_PLUGIN_IMAGE}" "docker://${UPLOADREGISTRY}/${CONSOLE_PLUGIN_IMAGE}"

.PHONY: console-integration-tests
console-integration-tests: ## Run console integration tests (requires running cluster)
@echo "Running console integration tests..."
cd console; ./scripts/test-prow-e2e.sh
1 change: 1 addition & 0 deletions console/integration-tests/cypress.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ module.exports = defineConfig({
openMode: 0,
},
e2e: {
testIsolation: false,
setupNodeEvents(on, config) {
return require('./plugins/index.ts')(on, config);
},
Expand Down
9 changes: 9 additions & 0 deletions console/integration-tests/support/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ declare global {
interface Chainable {
login(username?: string, password?: string): Chainable<Element>;
logout(): Chainable<Element>;
dismissTour(): Chainable<Element>;
}
}
}
Expand Down Expand Up @@ -30,6 +31,14 @@ Cypress.Commands.add('login', (username: string, password: string) => {
});
});

Cypress.Commands.add('dismissTour', () => {
cy.get('body').then(($body) => {
if ($body.find('[data-test="tour-step-footer-secondary"]').length > 0) {
cy.get('[data-test="tour-step-footer-secondary"]').contains('Skip tour').click();
}
});
});

Cypress.Commands.add('logout', () => {
// Check if auth is disabled (for a local development environment).
cy.window().then((win) => {
Expand Down
72 changes: 72 additions & 0 deletions console/integration-tests/tests/install-pattern-page.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
const navigateToInstallPage = () => {
cy.visit('/patterns');
cy.get('.patterns-operator__card', { timeout: 60000 }).should('exist');
cy.get('.patterns-operator__card-actions')
.contains('button:not(:disabled)', 'Install')
.first()
.click();
cy.contains('h1', 'Install Pattern', { timeout: 60000 }).should('be.visible');
};

describe('Install Pattern Page', () => {
before(function () {
cy.login();
cy.dismissTour();

// Check if Install is available; skip the entire suite if not
cy.visit('/patterns');
cy.get('.patterns-operator__card', { timeout: 60000 }).should('exist');
cy.get('body').then(($body) => {
const installBtn = $body.find(
'.patterns-operator__card-actions button:not(:disabled):contains("Install")',
);
if (installBtn.length === 0) {
this.skip();
}
});
});

after(() => {
cy.logout();
});

it('displays the Install Pattern title', () => {
navigateToInstallPage();
cy.contains('h1', 'Install Pattern').should('be.visible');
});

it('form fields are pre-populated from catalog data', () => {
navigateToInstallPage();
cy.get('#pattern-name').invoke('val').should('not.be.empty');
cy.get('#pattern-target-repo').invoke('val').should('not.be.empty');
cy.get('#pattern-target-revision').should('have.value', 'main');
});

it('target repo is disabled by default', () => {
navigateToInstallPage();
cy.get('#pattern-target-repo').should('be.disabled');
});

it('use-own-fork checkbox enables the target repo field', () => {
navigateToInstallPage();
cy.get('#pattern-target-repo').should('be.disabled');
cy.get('#use-own-fork').check();
cy.get('#pattern-target-repo').should('not.be.disabled');
cy.get('#use-own-fork').uncheck();
cy.get('#pattern-target-repo').should('be.disabled');
});

it('has Install and Cancel buttons', () => {
navigateToInstallPage();
cy.contains('button', 'Install').scrollIntoView().should('be.visible');
cy.contains('button', 'Cancel').scrollIntoView().should('be.visible');
});

it('Cancel button returns to the catalog', () => {
navigateToInstallPage();
cy.contains('button', 'Cancel').click();
cy.url().should('include', '/patterns');
cy.url().should('not.include', '/install');
cy.contains('h1', 'Pattern Catalog', { timeout: 60000 }).should('be.visible');
});
});
135 changes: 96 additions & 39 deletions console/integration-tests/tests/pattern-catalog-page.cy.ts
Original file line number Diff line number Diff line change
@@ -1,53 +1,110 @@
import { checkErrors } from '../support';
const visitCatalog = () => {
cy.visit('/patterns');
cy.get('.patterns-operator__card', { timeout: 60000 }).should('exist');
};

const PLUGIN_NAME = 'patterns-operator-console-plugin';
export const isLocalDevEnvironment = Cypress.config('baseUrl').includes('localhost');
describe('Pattern Catalog Page', () => {
before(() => {
cy.login();
cy.dismissTour();
});

// Check if console plugin is installed and enabled (operator-managed)
const checkPluginInstalled = () => {
cy.visit('/k8s/cluster/operator.openshift.io~v1~Console/cluster/console-plugins');
cy.get(`[data-test="${PLUGIN_NAME}-status"]`).should('include.text', 'loaded');
};
after(() => {
cy.logout();
});

// For operator-managed deployment, we just need to verify the plugin exists
const verifyOperatorDeployment = () => {
cy.exec('oc get consoleplugin patterns-operator-console-plugin', {
failOnNonZeroExit: false,
}).then((result) => {
if (result.code !== 0) {
cy.log('Console plugin not found - operator may not be installed');
} else {
cy.log('Console plugin found via operator deployment');
}
it('displays the page title', () => {
cy.visit('/patterns');
cy.contains('h1', 'Pattern Catalog', { timeout: 60000 }).should('be.visible');
});
};

describe('Console plugin template test', () => {
before(() => {
cy.login();
cy.get(`[data-test="tour-step-footer-secondary"]`).contains('Skip tour').click();
it('loads and displays pattern cards', () => {
visitCatalog();
cy.get('.patterns-operator__card').should('have.length.greaterThan', 0);
});

if (!isLocalDevEnvironment) {
console.log('Verifying operator-managed console plugin deployment');
verifyOperatorDeployment();
} else {
console.log('Local development environment - assuming plugin is running via yarn start');
}
it('pattern cards show tier labels', () => {
visitCatalog();
cy.get('.patterns-operator__card').first().within(() => {
cy.get('.pf-v6-c-label').should('exist');
});
});

afterEach(() => {
checkErrors();
it('at least one pattern card displays a description', () => {
visitCatalog();
cy.get('.patterns-operator__card-description')
.should('have.length.greaterThan', 0)
.first()
.invoke('text')
.should('not.be.empty');
});

after(() => {
// No cleanup needed for operator-managed deployment
cy.logout();
it('pattern cards have external Docs and Repo links', () => {
visitCatalog();
cy.get('.patterns-operator__card-links').first().within(() => {
cy.contains('a', 'Docs')
.should('have.attr', 'target', '_blank')
.and('have.attr', 'href');
cy.contains('a', 'Repo')
.should('have.attr', 'target', '_blank')
.and('have.attr', 'href');
});
});

it('pattern cards have action buttons', () => {
visitCatalog();
cy.get('.patterns-operator__card-actions').first().within(() => {
cy.get('button').should('have.length.greaterThan', 0);
});
});

it('tier filter dropdown shows all tier options', () => {
visitCatalog();
// Default filter shows "Maintained"; click the toggle button
cy.contains('button', 'Maintained').click();
// Options are capitalized ("Tested", "Sandbox") and unique to the dropdown
cy.contains('Tested').should('be.visible');
cy.contains('Sandbox').should('be.visible');
// Close dropdown by clicking the toggle again
cy.contains('button', 'Maintained').click();
});

it('selecting all tiers shows at least as many cards as maintained only', () => {
visitCatalog();
cy.get('.patterns-operator__card').its('length').then((maintainedCount) => {
// Open filter and add Tested
cy.contains('button', 'Maintained').click();
cy.contains('Tested').click();
// Dropdown may close after selection; re-open to add Sandbox
cy.contains('button', /Maintained/).click();
cy.contains('Sandbox').click();
// Close dropdown
cy.get('body').click(0, 0);
// With more tiers selected, card count should be >= maintained only
cy.get('.patterns-operator__card').should('have.length.gte', maintainedCount);
});
});

it('clicking Install navigates to the install page', () => {
visitCatalog();
cy.get('body').then(($body) => {
const installBtn = $body.find('.patterns-operator__card-actions button:not(:disabled):contains("Install")');
if (installBtn.length === 0) {
cy.log('No Install button available (a pattern may already be installed)');
return;
}
cy.get('.patterns-operator__card-actions')
.contains('button:not(:disabled)', 'Install')
.first()
.click();
cy.url().should('include', '/patterns/install/');
cy.contains('h1', 'Install Pattern', { timeout: 60000 }).should('be.visible');
});
});

it('Verify the example page title', () => {
cy.get('[data-quickstart-id="qs-nav-home"]').click();
cy.get('[data-test="nav"]').contains('Plugin Example').click();
cy.url().should('include', '/example');
cy.get('[data-test="pattern-catalog-page-title"]').should('contain', 'Pattern Catalog');
it('Patterns section is visible in the sidebar navigation', () => {
cy.visit('/patterns');
cy.contains('h1', 'Pattern Catalog', { timeout: 60000 }).should('be.visible');
cy.get('nav').contains('Patterns').should('be.visible');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@ trap copyArtifacts EXIT

# don't log kubeadmin-password
set +x
BRIDGE_KUBEADMIN_PASSWORD="$(cat "${KUBEADMIN_PASSWORD_FILE:-${INSTALLER_DIR}/auth/kubeadmin-password}")"
if [ -z "${KUBEADMIN_PASSWORD:-}" ]; then
echo "ERROR: KUBEADMIN_PASSWORD is not set" >&2
exit 1
fi
BRIDGE_KUBEADMIN_PASSWORD="${KUBEADMIN_PASSWORD}"
export BRIDGE_KUBEADMIN_PASSWORD
set -x
BRIDGE_BASE_ADDRESS="$(oc get consoles.config.openshift.io cluster -o jsonpath='{.status.consoleURL}')"
Expand Down
4 changes: 2 additions & 2 deletions console/src/components/PatternCatalogPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -202,10 +202,10 @@ export default function PatternCatalogPage() {
<PageSection>
{catalogImage ? (
<Tooltip content={`${t('Catalog source')}: ${catalogImage}`}>
<Title headingLevel="h1" style={{ display: 'inline-block' }}>{t('Pattern Catalog')}</Title>
<Title headingLevel="h1" data-test="pattern-catalog-page-title" style={{ display: 'inline-block' }}>{t('Pattern Catalog')}</Title>
</Tooltip>
) : (
<Title headingLevel="h1">{t('Pattern Catalog')}</Title>
<Title headingLevel="h1" data-test="pattern-catalog-page-title">{t('Pattern Catalog')}</Title>
)}
</PageSection>
{catalogDescription && (
Expand Down