Skip to content

Commit

Permalink
Add feature for defining model specifications (#1221)
Browse files Browse the repository at this point in the history
* use more explicit variables for specifications
* add "danger" button styling
* navigate to edit model after creation
* subscribe to model changes
* reset plan creation stores upon navigating away to clear plan creation error message
  • Loading branch information
duranb authored May 3, 2024
1 parent 42acb71 commit 9a88041
Show file tree
Hide file tree
Showing 51 changed files with 2,987 additions and 360 deletions.
10 changes: 5 additions & 5 deletions e2e-tests/fixtures/Constraints.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { expect, type Locator, type Page } from '@playwright/test';
import { adjectives, animals, colors, uniqueNamesGenerator } from 'unique-names-generator';
import { fillEditorText } from '../utilities/editor.js';
import { Models } from './Models.js';

export class Constraints {
closeButton: Locator;
Expand All @@ -19,10 +18,7 @@ export class Constraints {
tableRow: Locator;
tableRowDeleteButton: Locator;

constructor(
public page: Page,
public models: Models,
) {
constructor(public page: Page) {
this.constraintName = uniqueNamesGenerator({ dictionaries: [adjectives, colors, animals] });
this.updatePage(page);
}
Expand Down Expand Up @@ -83,6 +79,10 @@ export class Constraints {
await this.page.waitForTimeout(250);
}

async gotoNew() {
await this.page.goto('/constraints/new', { waitUntil: 'networkidle' });
}

updatePage(page: Page): void {
this.closeButton = page.locator(`button:has-text("Close")`);
this.confirmModal = page.locator(`.modal:has-text("Delete Constraint")`);
Expand Down
134 changes: 134 additions & 0 deletions e2e-tests/fixtures/Model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { expect, type Locator, type Page } from '@playwright/test';
import { Constraints } from './Constraints.js';
import { Models } from './Models.js';
import { SchedulingConditions } from './SchedulingConditions.js';
import { SchedulingGoals } from './SchedulingGoals.js';

export class Model {
associationTable: Locator;
closeButton: Locator;
conditionRadioButton: Locator;
confirmModal: Locator;
confirmModalDeleteButton: Locator;
constraintRadioButton: Locator;
deleteButton: Locator;
descriptionInput: Locator;
goalRadioButton: Locator;
libraryRadioButton: Locator;
modelRadioButton: Locator;
nameInput: Locator;
newPlanButton: Locator;
saveButton: Locator;
versionInput: Locator;

constructor(
public page: Page,
public models: Models,
public constraints: Constraints,
public schedulingGoals: SchedulingGoals,
public schedulingConditions: SchedulingConditions,
) {
this.updatePage(page);
}

async close() {
await this.closeButton.click();
await expect(this.page).toHaveURL('.*/models$');
}

async deleteModel() {
await expect(this.confirmModal).not.toBeVisible();
await this.deleteButton.click();
await this.confirmModal.waitFor({ state: 'attached' });
await this.confirmModal.waitFor({ state: 'visible' });
await expect(this.confirmModal).toBeVisible();

await expect(this.confirmModalDeleteButton).toBeVisible();
await this.confirmModalDeleteButton.click();
}

/**
* Wait for Hasura events to finish seeding the database after a model is created.
* If we do not wait then navigation to the plan will fail because the data is not there yet.
* If your tests fail then the timeout might be too short.
* Re-run the tests and increase the timeout if you get consistent failures.
*/
async goto() {
await this.page.waitForTimeout(1200);
await this.page.goto(`/models/${this.models.modelId}`, { waitUntil: 'networkidle' });
await this.page.waitForTimeout(250);
}

async saveModel() {
await expect(this.saveButton).toBeVisible();
await this.saveButton.click();
await expect(this.saveButton).toBeVisible();
}

async switchToConditions() {
await this.conditionRadioButton.click();
this.updatePage(this.page);
await expect(this.page.getByText('Condition - Definition')).toBeVisible();
}

async switchToConstraints() {
await this.constraintRadioButton.click();
this.updatePage(this.page);
await expect(this.page.getByText('Constraint - Definition')).toBeVisible();
}

async switchToGoals() {
await this.goalRadioButton.click();
this.updatePage(this.page);
await expect(this.page.getByText('Goal - Definition')).toBeVisible();
}

async switchToLibraryView() {
await this.libraryRadioButton.click();
this.updatePage(this.page);
await expect(this.associationTable).toBeVisible();
}

async switchToModelView() {
await this.modelRadioButton.click();
this.updatePage(this.page);
await expect(this.associationTable).not.toBeVisible();
}

async updateDescription(modelDescription: string) {
await this.descriptionInput.click();
await this.descriptionInput.fill(modelDescription);
await expect(this.descriptionInput).toHaveValue(modelDescription);
}

async updateName(modelName: string) {
await this.nameInput.click();
await this.nameInput.fill(modelName);
await expect(this.nameInput).toHaveValue(modelName);
}

async updatePage(page: Page): Promise<Promise<void>> {
this.closeButton = page.getByRole('button', { name: 'Close' });
this.conditionRadioButton = page.getByRole('button', { name: 'Conditions' });
this.constraintRadioButton = page.getByRole('button', { name: 'Constraints' });
this.deleteButton = page.getByRole('button', { name: 'Delete model' });
this.descriptionInput = page.locator('textarea[name="description"]');
this.goalRadioButton = page.getByRole('button', { name: 'Goals' });
this.goalRadioButton = page.getByRole('button', { name: 'Goals' });
this.libraryRadioButton = page.getByRole('button', { name: 'Library' });
this.modelRadioButton = page.getByRole('button', { exact: true, name: 'Model' });
this.nameInput = page.locator('input[name="name"]');
this.newPlanButton = page.getByRole('button', { name: 'New plan with model' });
this.versionInput = page.locator('input[name="version"]');
this.associationTable = page.getByRole('treegrid');
this.saveButton = page.getByRole('button', { name: 'Save' });
this.confirmModal = page.locator(`.modal:has-text("Delete Model")`);
this.confirmModalDeleteButton = page.locator(`.modal:has-text("Delete Model") >> button:has-text("Delete")`);
}

async updateVersion(modelVersion: string) {
await this.nameInput.focus();
await this.nameInput.fill(modelVersion);
await expect(this.nameInput).toHaveValue(modelVersion);
}
}
12 changes: 12 additions & 0 deletions e2e-tests/fixtures/Models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,18 @@ export class Models {
confirmModal: Locator;
confirmModalDeleteButton: Locator;
createButton: Locator;
createPlanButton: Locator;
creatingButton: Locator;
inputFile: Locator;
inputName: Locator;
inputVersion: Locator;
jarPath: string = 'e2e-tests/data/banananation-develop.jar'; // TODO: Pull .jar from aerie project.
modelId: string;
modelName: string;
modelVersion: string = '1.0.0';
tableRow: Locator;
tableRowDeleteButton: Locator;
tableRowModelId: Locator;

constructor(public page: Page) {
this.modelName = uniqueNamesGenerator({ dictionaries: [adjectives, colors, animals] });
Expand All @@ -32,9 +35,16 @@ export class Models {
await this.fillInputVersion();
await this.fillInputFile();
await this.createButton.click();
await expect(this.page).toHaveURL(/.*\/models\/\d+/);
await this.goto();
await this.tableRow.waitFor({ state: 'attached' });
await this.tableRow.waitFor({ state: 'visible' });
await expect(this.tableRow).toBeVisible();
await expect(this.tableRowModelId).toBeVisible();
const el = await this.tableRowModelId.elementHandle();
if (el) {
this.modelId = (await el.textContent()) as string;
}
}

async deleteModel() {
Expand Down Expand Up @@ -88,6 +98,7 @@ export class Models {
this.confirmModalDeleteButton = page.locator(`.modal:has-text("Delete Model") >> button:has-text("Delete")`);
this.createButton = page.getByRole('button', { name: 'Create' });
this.creatingButton = page.getByRole('button', { name: 'Creating...' });
this.createPlanButton = page.getByRole('button', { name: 'New plan with model' });
this.inputFile = page.locator('input[name="file"]');
this.inputName = page.locator('input[name="name"]');
this.inputVersion = page.locator('input[name="version"]');
Expand All @@ -96,5 +107,6 @@ export class Models {
this.tableRowDeleteButton = page.locator(
`.ag-row:has-text("${this.modelName}") >> button[aria-label="Delete Model"]`,
);
this.tableRowModelId = page.locator(`.ag-row:has-text("${this.modelName}") > div >> nth=0`);
}
}
10 changes: 5 additions & 5 deletions e2e-tests/fixtures/SchedulingConditions.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { expect, type Locator, type Page } from '@playwright/test';
import { adjectives, animals, colors, uniqueNamesGenerator } from 'unique-names-generator';
import { fillEditorText } from '../utilities/editor.js';
import { Models } from './Models.js';

export class SchedulingConditions {
closeButton: Locator;
Expand All @@ -20,10 +19,7 @@ export class SchedulingConditions {
tableRow: Locator;
tableRowDeleteButton: Locator;

constructor(
public page: Page,
public models: Models,
) {
constructor(public page: Page) {
this.conditionName = uniqueNamesGenerator({ dictionaries: [adjectives, colors, animals] });
this.updatePage(page);
}
Expand Down Expand Up @@ -85,6 +81,10 @@ export class SchedulingConditions {
await this.page.waitForSelector(`input[placeholder="Filter conditions"]`, { state: 'attached' });
}

async gotoNew() {
await this.page.goto('/scheduling/conditions/new', { waitUntil: 'networkidle' });
}

updatePage(page: Page): void {
this.closeButton = page.locator(`button:has-text("Close")`);
this.confirmModal = page.locator(`.modal:has-text("Delete Scheduling Condition")`);
Expand Down
10 changes: 5 additions & 5 deletions e2e-tests/fixtures/SchedulingGoals.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { expect, type Locator, type Page } from '@playwright/test';
import { fillEditorText } from '../utilities/editor.js';
import { Models } from './Models.js';

export class SchedulingGoals {
closeButton: Locator;
Expand All @@ -18,10 +17,7 @@ export class SchedulingGoals {
tableRowDeleteButtonSelector: (goalName: string) => Locator;
tableRowSelector: (goalName: string) => Locator;

constructor(
public page: Page,
public models: Models,
) {
constructor(public page: Page) {
this.updatePage(page);
}

Expand Down Expand Up @@ -83,6 +79,10 @@ export class SchedulingGoals {
await this.page.waitForSelector(`input[placeholder="Filter goals"]`, { state: 'attached' });
}

async gotoNew() {
await this.page.goto('/scheduling/goals/new', { waitUntil: 'networkidle' });
}

updatePage(page: Page): void {
this.closeButton = page.locator(`button:has-text("Close")`);
this.confirmModal = page.locator(`.modal:has-text("Delete Scheduling Goal")`);
Expand Down
6 changes: 3 additions & 3 deletions e2e-tests/tests/constraints.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ test.beforeAll(async ({ browser }) => {

models = new Models(page);
plans = new Plans(page, models);
constraints = new Constraints(page, models);
schedulingConditions = new SchedulingConditions(page, models);
schedulingGoals = new SchedulingGoals(page, models);
constraints = new Constraints(page);
schedulingConditions = new SchedulingConditions(page);
schedulingGoals = new SchedulingGoals(page);
plan = new Plan(page, plans, constraints, schedulingGoals, schedulingConditions);

await models.goto();
Expand Down
Loading

0 comments on commit 9a88041

Please sign in to comment.