import { Page } from '@playwright/test' // Test data fixtures export const testFormData = { // Basic organization info orgName: 'Test Cooperative Solutions', orgType: 'Worker Cooperative', memberCount: '12', // Core values (checkboxes) coreValues: ['Mutual Care', 'Transparency', 'Accountability'], customValues: 'We prioritize collective decision-making and equitable resource distribution.', // Conflict types (checkboxes) conflictTypes: [ 'Interpersonal disputes between members', 'Code of Conduct violations', 'Work performance issues', 'Financial disagreements' ], // Resolution approach approach: 'restorative', anonymousReporting: true, // Report receivers (checkboxes) reportReceivers: [ 'Designated conflict resolution committee', 'Executive Director(s)', 'Designated staff liaison' ], // Mediator structure mediatorType: 'Standing committee', supportPeople: true, // Process steps (checkboxes) processSteps: [ 'Initial report/complaint received', 'Acknowledgment sent to complainant', 'Initial assessment by designated party', 'Informal resolution attempted', 'Formal investigation if needed', 'Resolution/decision reached', 'Follow-up and monitoring' ], // Timeline initialResponse: 'Within 48 hours', resolutionTarget: '2 weeks', // Available actions (checkboxes) availableActions: [ 'Verbal warning', 'Written warning', 'Required training/education', 'Mediation facilitation', 'Temporary suspension' ], // Documentation and confidentiality docLevel: 'Detailed - comprehensive documentation', confidentiality: 'Need-to-know basis', retention: '7 years', appealProcess: true, training: 'All committee members must complete conflict resolution training annually.', // Implementation reviewSchedule: 'Annually', amendments: 'Consent process (no objections)', createdDate: '2024-03-15', reviewDate: '2025-03-15', // Enhanced sections reflectionPeriod: '24-48 hours before complaint', customReflectionPrompts: 'Consider: What outcome would best serve the collective? How can this become a learning opportunity?', requireDirectAttempt: true, documentDirectResolution: true, // Communication channels (checkboxes) communicationChannels: [ 'Asynchronous text (Slack, email)', 'Synchronous text (planned chat session)', 'Audio call or huddle', 'Video conference' ], // Internal advisor internalAdvisorType: 'Committee-designated advisor', staffLiaison: 'Operations Coordinator', boardChairRole: 'First contact for ED complaints', // Formal complaints formalComplaintElements: [ 'The complainant\'s name', 'The respondent\'s name', 'Detailed information about the issue (what, where, when)', 'Details of all prior resolution attempts', 'The specific outcome(s) the complainant is seeking' ], formalAcknowledgmentTime: 'Within 48 hours', formalReviewTime: '1 month', requireExternalAdvice: true, // Settlement requireMinutesOfSettlement: true, settlementConfidentiality: 'Restricted to parties and advisors', conflictFileRetention: '7 years', // External resources includeHumanRights: true, additionalResources: 'Local Community Mediation Center: (555) 123-4567\nWorker Cooperative Legal Aid: www.coop-legal.org', acknowledgments: 'This policy was developed with input from the Media Arts Network of Ontario and local cooperative development specialists.', // Special circumstances (checkboxes) specialCircumstances: [ 'Include immediate removal protocol for safety threats', 'Reference external reporting options (Human Rights Tribunal, etc.)', 'Include anti-retaliation provisions' ] } // Utility functions for form interaction export class ConflictResolutionFormHelper { constructor(private page: Page) {} async goto() { await this.page.goto('/templates/conflict-resolution-framework') await this.page.waitForLoadState('networkidle') } async fillBasicInfo(data: typeof testFormData) { // Fill text inputs await this.page.fill('input[placeholder*="organization name"]', data.orgName) // Handle USelect component for organization type const orgTypeButton = this.page.locator('button:has-text("Select organization type"), [role="combobox"]:has-text("Select organization type")').first() await orgTypeButton.click() await this.page.locator(`text="${data.orgType}"`).click() await this.page.fill('input[type="number"]', data.memberCount) } async selectCheckboxes(sectionSelector: string, items: string[]) { for (const item of items) { const checkbox = this.page.locator(`${sectionSelector} label:has-text("${item}") input[type="checkbox"]`) await checkbox.check() } } async fillCoreValues(data: typeof testFormData) { // Enable values section if it has a toggle const valuesToggle = this.page.locator('.toggle:near(:text("Guiding Principles"))') if (await valuesToggle.isVisible()) { await valuesToggle.click() } // Select core values checkboxes for (const value of data.coreValues) { await this.page.locator(`label:has-text("${value}") input[type="checkbox"]`).check() } // Fill custom values textarea await this.page.fill('textarea[placeholder*="values"], textarea[placeholder*="principles"]', data.customValues) } async fillConflictTypes(data: typeof testFormData) { for (const type of data.conflictTypes) { await this.page.locator(`label:has-text("${type}") input[type="checkbox"]`).check() } } async fillApproach(data: typeof testFormData) { await this.page.locator(`input[value="${data.approach}"]`).check() if (data.anonymousReporting) { await this.page.locator('input[id*="anonymous"], label:has-text("anonymous") input').check() } } async fillReportReceivers(data: typeof testFormData) { for (const receiver of data.reportReceivers) { await this.page.locator(`label:has-text("${receiver}") input[type="checkbox"]`).check() } } async fillMediatorStructure(data: typeof testFormData) { await this.page.selectOption('select:has-text("mediator"), select[placeholder*="mediator"]', data.mediatorType) if (data.supportPeople) { await this.page.locator('label:has-text("support people"), label:has-text("Support people") input').check() } } async fillProcessSteps(data: typeof testFormData) { for (const step of data.processSteps) { await this.page.locator(`label:has-text("${step}") input[type="checkbox"]`).check() } } async fillTimeline(data: typeof testFormData) { await this.page.selectOption('select:has-text("response"), select[placeholder*="response"]', data.initialResponse) await this.page.selectOption('select:has-text("resolution"), select[placeholder*="target"]', data.resolutionTarget) } async fillAvailableActions(data: typeof testFormData) { for (const action of data.availableActions) { await this.page.locator(`label:has-text("${action}") input[type="checkbox"]`).check() } } async fillDocumentation(data: typeof testFormData) { await this.page.selectOption('select:has-text("documentation"), select[placeholder*="level"]', data.docLevel) await this.page.selectOption('select:has-text("confidentiality"),' , data.confidentiality) await this.page.selectOption('select:has-text("retention"),' , data.retention) if (data.appealProcess) { await this.page.locator('label:has-text("appeal") input[type="checkbox"]').check() } await this.page.fill('textarea[placeholder*="training"]', data.training) } async fillImplementation(data: typeof testFormData) { await this.page.selectOption('select:has-text("review"), select[placeholder*="schedule"]', data.reviewSchedule) await this.page.selectOption('select:has-text("amendment"),' , data.amendments) await this.page.fill('input[type="date"]:first-of-type', data.createdDate) await this.page.fill('input[type="date"]:last-of-type', data.reviewDate) } async fillEnhancedSections(data: typeof testFormData) { // Reflection section const reflectionToggle = this.page.locator('.toggle:near(:text("Reflection"))') if (await reflectionToggle.isVisible()) { await reflectionToggle.click() } await this.page.selectOption('select[placeholder*="reflection"]', data.reflectionPeriod) await this.page.fill('textarea[placeholder*="reflection"]', data.customReflectionPrompts) // Direct resolution section const directToggle = this.page.locator('.toggle:near(:text("Direct Resolution"))') if (await directToggle.isVisible()) { await directToggle.click() } for (const channel of data.communicationChannels) { await this.page.locator(`label:has-text("${channel}") input[type="checkbox"]`).check() } if (data.requireDirectAttempt) { await this.page.locator('label:has-text("require direct") input').check() } if (data.documentDirectResolution) { await this.page.locator('label:has-text("written record") input').check() } // RCP section await this.page.selectOption('select[placeholder*="internal advisor"]', data.internalAdvisorType) await this.page.fill('input[placeholder*="staff liaison"]', data.staffLiaison) await this.page.selectOption('select[placeholder*="board chair"]', data.boardChairRole) // Formal complaints for (const element of data.formalComplaintElements) { await this.page.locator(`label:has-text("${element}") input[type="checkbox"]`).check() } await this.page.selectOption('select[placeholder*="acknowledgment"]', data.formalAcknowledgmentTime) await this.page.selectOption('select[placeholder*="review time"]', data.formalReviewTime) if (data.requireExternalAdvice) { await this.page.locator('label:has-text("external") input').check() } // Settlement if (data.requireMinutesOfSettlement) { await this.page.locator('label:has-text("Minutes of Settlement") input').check() } await this.page.selectOption('select[placeholder*="confidentiality"]', data.settlementConfidentiality) await this.page.selectOption('select[placeholder*="retention"]', data.conflictFileRetention) // External resources const resourcesToggle = this.page.locator('.toggle:near(:text("External Resources"))') if (await resourcesToggle.isVisible()) { await resourcesToggle.click() } if (data.includeHumanRights) { await this.page.locator('label:has-text("Human Rights") input').check() } await this.page.fill('textarea[placeholder*="external resources"]', data.additionalResources) await this.page.fill('textarea[placeholder*="acknowledgment"]', data.acknowledgments) // Special circumstances const specialToggle = this.page.locator('.toggle:near(:text("Special"))') if (await specialToggle.isVisible()) { await specialToggle.click() } for (const circumstance of data.specialCircumstances) { await this.page.locator(`label:has-text("${circumstance}") input[type="checkbox"]`).check() } } async downloadMarkdown(): Promise { // Click download markdown button const downloadPromise = this.page.waitForDownload() await this.page.locator('button:has-text("MARKDOWN"), button:has-text("Download Policy")').click() const download = await downloadPromise // Read downloaded file content const stream = await download.createReadStream() const chunks: Buffer[] = [] return new Promise((resolve, reject) => { stream.on('data', chunk => chunks.push(chunk)) stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8'))) stream.on('error', reject) }) } } // Markdown parsing and validation utilities export class MarkdownValidator { constructor(private markdown: string) {} validateOrganizationInfo(expectedData: typeof testFormData) { const errors: string[] = [] // Check organization name in title if (!this.markdown.includes(`# ${expectedData.orgName} Conflict Resolution Policy`)) { errors.push(`Title should contain "${expectedData.orgName}"`) } // Check organization type context const hasCooperativeReferences = expectedData.orgType.includes('Cooperative') const hasMembers = this.markdown.includes('members') if (hasCooperativeReferences && !hasMembers) { errors.push('Cooperative org type should reference members') } return errors } validateCoreValues(expectedData: typeof testFormData) { const errors: string[] = [] // Check each core value appears for (const value of expectedData.coreValues) { if (!this.markdown.includes(value)) { errors.push(`Core value "${value}" not found in markdown`) } } // Check custom values text if (expectedData.customValues && !this.markdown.includes(expectedData.customValues)) { errors.push('Custom values text not found in markdown') } return errors } validateConflictTypes(expectedData: typeof testFormData) { const errors: string[] = [] // Find the conflict types table const tableMatch = this.markdown.match(/\| \*\*Who Can File\*\* \| \*\*Type of Complaint\*\* \| \*\*Policy Reference\*\* \| \*\*Additional Notes\*\* \|(.*?)(?=\n\n|\n#)/s) if (!tableMatch) { errors.push('Conflict types table not found') return errors } const tableContent = tableMatch[1] // Check each selected conflict type appears in table for (const type of expectedData.conflictTypes) { if (!tableContent.includes(type)) { errors.push(`Conflict type "${type}" not found in table`) } } return errors } validateProcessSteps(expectedData: typeof testFormData) { const errors: string[] = [] for (const step of expectedData.processSteps) { if (!this.markdown.includes(step)) { errors.push(`Process step "${step}" not found`) } } return errors } validateTimeline(expectedData: typeof testFormData) { const errors: string[] = [] // Check response time if (!this.markdown.includes(expectedData.initialResponse.toLowerCase())) { errors.push(`Initial response time "${expectedData.initialResponse}" not found`) } // Check resolution target if (!this.markdown.includes(expectedData.resolutionTarget.toLowerCase())) { errors.push(`Resolution target "${expectedData.resolutionTarget}" not found`) } return errors } validateAvailableActions(expectedData: typeof testFormData) { const errors: string[] = [] for (const action of expectedData.availableActions) { if (!this.markdown.includes(action)) { errors.push(`Available action "${action}" not found`) } } return errors } validateEnhancedSections(expectedData: typeof testFormData) { const errors: string[] = [] // Check reflection section if (expectedData.customReflectionPrompts && !this.markdown.includes(expectedData.customReflectionPrompts)) { errors.push('Custom reflection prompts not found') } // Check communication channels for (const channel of expectedData.communicationChannels) { if (!this.markdown.includes(channel)) { errors.push(`Communication channel "${channel}" not found`) } } // Check staff liaison if (expectedData.staffLiaison && !this.markdown.includes(expectedData.staffLiaison)) { errors.push(`Staff liaison "${expectedData.staffLiaison}" not found`) } // Check formal complaint elements for (const element of expectedData.formalComplaintElements) { if (!this.markdown.includes(element)) { errors.push(`Formal complaint element "${element}" not found`) } } // Check external resources if (expectedData.additionalResources && !this.markdown.includes(expectedData.additionalResources)) { errors.push('Additional resources not found') } if (expectedData.acknowledgments && !this.markdown.includes(expectedData.acknowledgments)) { errors.push('Acknowledgments not found') } return errors } validateDates(expectedData: typeof testFormData) { const errors: string[] = [] if (expectedData.createdDate && !this.markdown.includes(expectedData.createdDate)) { errors.push(`Created date "${expectedData.createdDate}" not found`) } if (expectedData.reviewDate && !this.markdown.includes(expectedData.reviewDate)) { errors.push(`Review date "${expectedData.reviewDate}" not found`) } return errors } validateLanguageQuality() { const errors: string[] = [] // Check for common language issues we fixed if (this.markdown.includes("'s's")) { errors.push('Incorrect possessive form found ("\'s\'s")') } if (this.markdown.includes('within within')) { errors.push('Redundant "within within" found') } if (this.markdown.includes('members members')) { errors.push('Redundant word repetition found') } return errors } validateAll(expectedData: typeof testFormData) { const allErrors = [ ...this.validateOrganizationInfo(expectedData), ...this.validateCoreValues(expectedData), ...this.validateConflictTypes(expectedData), ...this.validateProcessSteps(expectedData), ...this.validateTimeline(expectedData), ...this.validateAvailableActions(expectedData), ...this.validateEnhancedSections(expectedData), ...this.validateDates(expectedData), ...this.validateLanguageQuality() ] return allErrors } }