diff --git a/testing/recordings/correspondence_api_1772337223924.webp b/testing/recordings/correspondence_api_1772337223924.webp new file mode 100644 index 0000000..7cd05e4 Binary files /dev/null and b/testing/recordings/correspondence_api_1772337223924.webp differ diff --git a/testing/recordings/dashboard_test_1772336335244.webp b/testing/recordings/dashboard_test_1772336335244.webp new file mode 100644 index 0000000..1dc2eb0 Binary files /dev/null and b/testing/recordings/dashboard_test_1772336335244.webp differ diff --git a/testing/recordings/fertilizer_test_1772336994610.webp b/testing/recordings/fertilizer_test_1772336994610.webp new file mode 100644 index 0000000..647e280 Binary files /dev/null and b/testing/recordings/fertilizer_test_1772336994610.webp differ diff --git a/testing/recordings/field_detail_test_1772336588494.webp b/testing/recordings/field_detail_test_1772336588494.webp new file mode 100644 index 0000000..cf65945 Binary files /dev/null and b/testing/recordings/field_detail_test_1772336588494.webp differ diff --git a/testing/recordings/login_flow_1772336274097.webp b/testing/recordings/login_flow_1772336274097.webp new file mode 100644 index 0000000..0304659 Binary files /dev/null and b/testing/recordings/login_flow_1772336274097.webp differ diff --git a/testing/recordings/reports_allocation_1772336843870.webp b/testing/recordings/reports_allocation_1772336843870.webp new file mode 100644 index 0000000..bb6df0a Binary files /dev/null and b/testing/recordings/reports_allocation_1772336843870.webp differ diff --git a/testing/recordings/rules_reports_test_1772337104257.webp b/testing/recordings/rules_reports_test_1772337104257.webp new file mode 100644 index 0000000..120d41d Binary files /dev/null and b/testing/recordings/rules_reports_test_1772337104257.webp differ diff --git a/testing/recordings/weather_page_test_1772336922550.webp b/testing/recordings/weather_page_test_1772336922550.webp new file mode 100644 index 0000000..604d25b Binary files /dev/null and b/testing/recordings/weather_page_test_1772336922550.webp differ diff --git a/testing/screenshots/01_dashboard/dashboard_overview_1772336367646.png b/testing/screenshots/01_dashboard/dashboard_overview_1772336367646.png new file mode 100644 index 0000000..2bfb2cd Binary files /dev/null and b/testing/screenshots/01_dashboard/dashboard_overview_1772336367646.png differ diff --git a/testing/screenshots/02_fields/field_1_not_found_1772336794349.png b/testing/screenshots/02_fields/field_1_not_found_1772336794349.png new file mode 100644 index 0000000..2197cc5 Binary files /dev/null and b/testing/screenshots/02_fields/field_1_not_found_1772336794349.png differ diff --git a/testing/screenshots/02_fields/field_detail_bottom_1772336786725.png b/testing/screenshots/02_fields/field_detail_bottom_1772336786725.png new file mode 100644 index 0000000..eb94086 Binary files /dev/null and b/testing/screenshots/02_fields/field_detail_bottom_1772336786725.png differ diff --git a/testing/screenshots/02_fields/field_detail_top_1772336614836.png b/testing/screenshots/02_fields/field_detail_top_1772336614836.png new file mode 100644 index 0000000..2197cc5 Binary files /dev/null and b/testing/screenshots/02_fields/field_detail_top_1772336614836.png differ diff --git a/testing/screenshots/02_fields/fields_correspondence_table_1772337255293.png b/testing/screenshots/02_fields/fields_correspondence_table_1772337255293.png new file mode 100644 index 0000000..e5cc505 Binary files /dev/null and b/testing/screenshots/02_fields/fields_correspondence_table_1772337255293.png differ diff --git a/testing/screenshots/02_fields/fields_list_page_1772336436720.png b/testing/screenshots/02_fields/fields_list_page_1772336436720.png new file mode 100644 index 0000000..81fd996 Binary files /dev/null and b/testing/screenshots/02_fields/fields_list_page_1772336436720.png differ diff --git a/testing/screenshots/03_allocation/allocation_filtered_and_sorted_1772337693866.png b/testing/screenshots/03_allocation/allocation_filtered_and_sorted_1772337693866.png new file mode 100644 index 0000000..60eaf15 Binary files /dev/null and b/testing/screenshots/03_allocation/allocation_filtered_and_sorted_1772337693866.png differ diff --git a/testing/screenshots/03_allocation/allocation_group_sort_1772337483556.png b/testing/screenshots/03_allocation/allocation_group_sort_1772337483556.png new file mode 100644 index 0000000..8489301 Binary files /dev/null and b/testing/screenshots/03_allocation/allocation_group_sort_1772337483556.png differ diff --git a/testing/screenshots/03_allocation/allocation_page_bottom_1772336878555.png b/testing/screenshots/03_allocation/allocation_page_bottom_1772336878555.png new file mode 100644 index 0000000..ef8649d Binary files /dev/null and b/testing/screenshots/03_allocation/allocation_page_bottom_1772336878555.png differ diff --git a/testing/screenshots/03_allocation/allocation_page_top_1772336872770.png b/testing/screenshots/03_allocation/allocation_page_top_1772336872770.png new file mode 100644 index 0000000..dd1ee4c Binary files /dev/null and b/testing/screenshots/03_allocation/allocation_page_top_1772336872770.png differ diff --git a/testing/screenshots/03_allocation/allocation_search_filter_1772337491356.png b/testing/screenshots/03_allocation/allocation_search_filter_1772337491356.png new file mode 100644 index 0000000..6c718f7 Binary files /dev/null and b/testing/screenshots/03_allocation/allocation_search_filter_1772337491356.png differ diff --git a/testing/screenshots/04_reports/chusankan_report_preview_jap_1772337836398.png b/testing/screenshots/04_reports/chusankan_report_preview_jap_1772337836398.png new file mode 100644 index 0000000..8361c8e Binary files /dev/null and b/testing/screenshots/04_reports/chusankan_report_preview_jap_1772337836398.png differ diff --git a/testing/screenshots/04_reports/report_preview_pdf_1772337160959.png b/testing/screenshots/04_reports/report_preview_pdf_1772337160959.png new file mode 100644 index 0000000..4099e6c Binary files /dev/null and b/testing/screenshots/04_reports/report_preview_pdf_1772337160959.png differ diff --git a/testing/screenshots/04_reports/reports_page_initial_1772336864933.png b/testing/screenshots/04_reports/reports_page_initial_1772336864933.png new file mode 100644 index 0000000..e997160 Binary files /dev/null and b/testing/screenshots/04_reports/reports_page_initial_1772336864933.png differ diff --git a/testing/screenshots/05_import/import_page_initial_1772336881986.png b/testing/screenshots/05_import/import_page_initial_1772336881986.png new file mode 100644 index 0000000..5ba843b Binary files /dev/null and b/testing/screenshots/05_import/import_page_initial_1772336881986.png differ diff --git a/testing/screenshots/06_weather/weather_monthly_summary_1772337721599.png b/testing/screenshots/06_weather/weather_monthly_summary_1772337721599.png new file mode 100644 index 0000000..7113ef2 Binary files /dev/null and b/testing/screenshots/06_weather/weather_monthly_summary_1772337721599.png differ diff --git a/testing/screenshots/06_weather/weather_page_absolute_top_1772336951833.png b/testing/screenshots/06_weather/weather_page_absolute_top_1772336951833.png new file mode 100644 index 0000000..3cd8e2d Binary files /dev/null and b/testing/screenshots/06_weather/weather_page_absolute_top_1772336951833.png differ diff --git a/testing/screenshots/06_weather/weather_page_bottom_1772336945515.png b/testing/screenshots/06_weather/weather_page_bottom_1772336945515.png new file mode 100644 index 0000000..0c244a6 Binary files /dev/null and b/testing/screenshots/06_weather/weather_page_bottom_1772336945515.png differ diff --git a/testing/screenshots/06_weather/weather_page_top_1772336941221.png b/testing/screenshots/06_weather/weather_page_top_1772336941221.png new file mode 100644 index 0000000..64494a9 Binary files /dev/null and b/testing/screenshots/06_weather/weather_page_top_1772336941221.png differ diff --git a/testing/screenshots/06_weather/weather_recent_14_days_1772337725153.png b/testing/screenshots/06_weather/weather_recent_14_days_1772337725153.png new file mode 100644 index 0000000..bbc5986 Binary files /dev/null and b/testing/screenshots/06_weather/weather_recent_14_days_1772337725153.png differ diff --git a/testing/screenshots/07_fertilizer/fertilizer_list_page_1772337024220.png b/testing/screenshots/07_fertilizer/fertilizer_list_page_1772337024220.png new file mode 100644 index 0000000..9738601 Binary files /dev/null and b/testing/screenshots/07_fertilizer/fertilizer_list_page_1772337024220.png differ diff --git a/testing/screenshots/07_fertilizer/fertilizer_masters_page_1772337064797.png b/testing/screenshots/07_fertilizer/fertilizer_masters_page_1772337064797.png new file mode 100644 index 0000000..e6ca071 Binary files /dev/null and b/testing/screenshots/07_fertilizer/fertilizer_masters_page_1772337064797.png differ diff --git a/testing/screenshots/08_mail/mail_history_page_1772337055980.png b/testing/screenshots/08_mail/mail_history_page_1772337055980.png new file mode 100644 index 0000000..235f897 Binary files /dev/null and b/testing/screenshots/08_mail/mail_history_page_1772337055980.png differ diff --git a/testing/screenshots/08_mail/mail_rules_page_1772337122287.png b/testing/screenshots/08_mail/mail_rules_page_1772337122287.png new file mode 100644 index 0000000..524de0f Binary files /dev/null and b/testing/screenshots/08_mail/mail_rules_page_1772337122287.png differ diff --git a/frontend/test-results/.last-run.json b/testing/subagent_generated/.last-run.json similarity index 100% rename from frontend/test-results/.last-run.json rename to testing/subagent_generated/.last-run.json diff --git a/testing/subagent_generated/e2e/screenshots/01-fertilizer-new-initial.png b/testing/subagent_generated/e2e/screenshots/01-fertilizer-new-initial.png new file mode 100644 index 0000000..80a4d05 Binary files /dev/null and b/testing/subagent_generated/e2e/screenshots/01-fertilizer-new-initial.png differ diff --git a/testing/subagent_generated/e2e/screenshots/01-initial.png b/testing/subagent_generated/e2e/screenshots/01-initial.png new file mode 100644 index 0000000..8fd99b9 Binary files /dev/null and b/testing/subagent_generated/e2e/screenshots/01-initial.png differ diff --git a/testing/subagent_generated/e2e/screenshots/02-after-login-fertilizer-new.png b/testing/subagent_generated/e2e/screenshots/02-after-login-fertilizer-new.png new file mode 100644 index 0000000..80a4d05 Binary files /dev/null and b/testing/subagent_generated/e2e/screenshots/02-after-login-fertilizer-new.png differ diff --git a/testing/subagent_generated/e2e/screenshots/02-after-login.png b/testing/subagent_generated/e2e/screenshots/02-after-login.png new file mode 100644 index 0000000..80a4d05 Binary files /dev/null and b/testing/subagent_generated/e2e/screenshots/02-after-login.png differ diff --git a/testing/subagent_generated/e2e/screenshots/02-after-variety-selection.png b/testing/subagent_generated/e2e/screenshots/02-after-variety-selection.png new file mode 100644 index 0000000..0f9c930 Binary files /dev/null and b/testing/subagent_generated/e2e/screenshots/02-after-variety-selection.png differ diff --git a/testing/subagent_generated/e2e/screenshots/03-404.png b/testing/subagent_generated/e2e/screenshots/03-404.png new file mode 100644 index 0000000..80a4d05 Binary files /dev/null and b/testing/subagent_generated/e2e/screenshots/03-404.png differ diff --git a/testing/subagent_generated/e2e/screenshots/03-current-state.png b/testing/subagent_generated/e2e/screenshots/03-current-state.png new file mode 100644 index 0000000..80a4d05 Binary files /dev/null and b/testing/subagent_generated/e2e/screenshots/03-current-state.png differ diff --git a/testing/subagent_generated/e2e/screenshots/03-fertilizer-picker-open.png b/testing/subagent_generated/e2e/screenshots/03-fertilizer-picker-open.png new file mode 100644 index 0000000..1ee3bfa Binary files /dev/null and b/testing/subagent_generated/e2e/screenshots/03-fertilizer-picker-open.png differ diff --git a/testing/subagent_generated/e2e/screenshots/04-after-adding-fertilizer.png b/testing/subagent_generated/e2e/screenshots/04-after-adding-fertilizer.png new file mode 100644 index 0000000..58d5767 Binary files /dev/null and b/testing/subagent_generated/e2e/screenshots/04-after-adding-fertilizer.png differ diff --git a/testing/subagent_generated/e2e/screenshots/05-matrix-table.png b/testing/subagent_generated/e2e/screenshots/05-matrix-table.png new file mode 100644 index 0000000..d71ae23 Binary files /dev/null and b/testing/subagent_generated/e2e/screenshots/05-matrix-table.png differ diff --git a/testing/subagent_generated/e2e/screenshots/06-final-full-page.png b/testing/subagent_generated/e2e/screenshots/06-final-full-page.png new file mode 100644 index 0000000..a4b7a44 Binary files /dev/null and b/testing/subagent_generated/e2e/screenshots/06-final-full-page.png differ diff --git a/testing/subagent_generated/e2e/screenshots/A-full-page-with-table.png b/testing/subagent_generated/e2e/screenshots/A-full-page-with-table.png new file mode 100644 index 0000000..58d5767 Binary files /dev/null and b/testing/subagent_generated/e2e/screenshots/A-full-page-with-table.png differ diff --git a/testing/subagent_generated/e2e/screenshots/B-table-header.png b/testing/subagent_generated/e2e/screenshots/B-table-header.png new file mode 100644 index 0000000..fa97f0a Binary files /dev/null and b/testing/subagent_generated/e2e/screenshots/B-table-header.png differ diff --git a/testing/subagent_generated/e2e/screenshots/C-fertilizer-column-header.png b/testing/subagent_generated/e2e/screenshots/C-fertilizer-column-header.png new file mode 100644 index 0000000..5d18379 Binary files /dev/null and b/testing/subagent_generated/e2e/screenshots/C-fertilizer-column-header.png differ diff --git a/testing/subagent_generated/e2e/screenshots/D-fertilizer-cell.png b/testing/subagent_generated/e2e/screenshots/D-fertilizer-cell.png new file mode 100644 index 0000000..32d5c80 Binary files /dev/null and b/testing/subagent_generated/e2e/screenshots/D-fertilizer-cell.png differ diff --git a/testing/subagent_generated/e2e/screenshots/f01-initial.png b/testing/subagent_generated/e2e/screenshots/f01-initial.png new file mode 100644 index 0000000..80a4d05 Binary files /dev/null and b/testing/subagent_generated/e2e/screenshots/f01-initial.png differ diff --git a/testing/subagent_generated/e2e/screenshots/f02-variety-selected.png b/testing/subagent_generated/e2e/screenshots/f02-variety-selected.png new file mode 100644 index 0000000..0f9c930 Binary files /dev/null and b/testing/subagent_generated/e2e/screenshots/f02-variety-selected.png differ diff --git a/testing/subagent_generated/e2e/screenshots/f03-fertilizer-added.png b/testing/subagent_generated/e2e/screenshots/f03-fertilizer-added.png new file mode 100644 index 0000000..0f9c930 Binary files /dev/null and b/testing/subagent_generated/e2e/screenshots/f03-fertilizer-added.png differ diff --git a/testing/subagent_generated/e2e/screenshots/f03-picker-open.png b/testing/subagent_generated/e2e/screenshots/f03-picker-open.png new file mode 100644 index 0000000..1ee3bfa Binary files /dev/null and b/testing/subagent_generated/e2e/screenshots/f03-picker-open.png differ diff --git a/testing/subagent_generated/e2e/screenshots/f04-after-fert-added.png b/testing/subagent_generated/e2e/screenshots/f04-after-fert-added.png new file mode 100644 index 0000000..f96ae37 Binary files /dev/null and b/testing/subagent_generated/e2e/screenshots/f04-after-fert-added.png differ diff --git a/testing/subagent_generated/e2e/screenshots/f04-no-table.png b/testing/subagent_generated/e2e/screenshots/f04-no-table.png new file mode 100644 index 0000000..0f9c930 Binary files /dev/null and b/testing/subagent_generated/e2e/screenshots/f04-no-table.png differ diff --git a/testing/subagent_generated/e2e/screenshots/f05-before-calc.png b/testing/subagent_generated/e2e/screenshots/f05-before-calc.png new file mode 100644 index 0000000..58535bd Binary files /dev/null and b/testing/subagent_generated/e2e/screenshots/f05-before-calc.png differ diff --git a/testing/subagent_generated/e2e/screenshots/f06-after-calc.png b/testing/subagent_generated/e2e/screenshots/f06-after-calc.png new file mode 100644 index 0000000..dacb710 Binary files /dev/null and b/testing/subagent_generated/e2e/screenshots/f06-after-calc.png differ diff --git a/testing/subagent_generated/e2e/screenshots/f06-no-calc-button.png b/testing/subagent_generated/e2e/screenshots/f06-no-calc-button.png new file mode 100644 index 0000000..0f9c930 Binary files /dev/null and b/testing/subagent_generated/e2e/screenshots/f06-no-calc-button.png differ diff --git a/testing/subagent_generated/e2e/screenshots/f07-table-scrolled.png b/testing/subagent_generated/e2e/screenshots/f07-table-scrolled.png new file mode 100644 index 0000000..c7908c5 Binary files /dev/null and b/testing/subagent_generated/e2e/screenshots/f07-table-scrolled.png differ diff --git a/testing/subagent_generated/e2e/screenshots/f09-final-full.png b/testing/subagent_generated/e2e/screenshots/f09-final-full.png new file mode 100644 index 0000000..0f9c930 Binary files /dev/null and b/testing/subagent_generated/e2e/screenshots/f09-final-full.png differ diff --git a/testing/subagent_generated/e2e/screenshots/f09-final.png b/testing/subagent_generated/e2e/screenshots/f09-final.png new file mode 100644 index 0000000..6b95d24 Binary files /dev/null and b/testing/subagent_generated/e2e/screenshots/f09-final.png differ diff --git a/testing/subagent_generated/e2e/screenshots/fertilizer-new-01-initial.png b/testing/subagent_generated/e2e/screenshots/fertilizer-new-01-initial.png new file mode 100644 index 0000000..80a4d05 Binary files /dev/null and b/testing/subagent_generated/e2e/screenshots/fertilizer-new-01-initial.png differ diff --git a/testing/subagent_generated/e2e/screenshots/fertilizer-new-02-variety-selected.png b/testing/subagent_generated/e2e/screenshots/fertilizer-new-02-variety-selected.png new file mode 100644 index 0000000..0f9c930 Binary files /dev/null and b/testing/subagent_generated/e2e/screenshots/fertilizer-new-02-variety-selected.png differ diff --git a/testing/subagent_generated/e2e/screenshots/fertilizer-new-03-fertilizer-added.png b/testing/subagent_generated/e2e/screenshots/fertilizer-new-03-fertilizer-added.png new file mode 100644 index 0000000..1ee3bfa Binary files /dev/null and b/testing/subagent_generated/e2e/screenshots/fertilizer-new-03-fertilizer-added.png differ diff --git a/testing/subagent_generated/e2e/screenshots/fertilizer-new-04-table-header.png b/testing/subagent_generated/e2e/screenshots/fertilizer-new-04-table-header.png new file mode 100644 index 0000000..1ee3bfa Binary files /dev/null and b/testing/subagent_generated/e2e/screenshots/fertilizer-new-04-table-header.png differ diff --git a/testing/subagent_generated/e2e/screenshots/fertilizer-new-05-no-calc-button.png b/testing/subagent_generated/e2e/screenshots/fertilizer-new-05-no-calc-button.png new file mode 100644 index 0000000..1ee3bfa Binary files /dev/null and b/testing/subagent_generated/e2e/screenshots/fertilizer-new-05-no-calc-button.png differ diff --git a/testing/subagent_generated/e2e/screenshots/fertilizer-new-07-final.png b/testing/subagent_generated/e2e/screenshots/fertilizer-new-07-final.png new file mode 100644 index 0000000..1ee3bfa Binary files /dev/null and b/testing/subagent_generated/e2e/screenshots/fertilizer-new-07-final.png differ diff --git a/testing/subagent_generated/e2e/screenshots/step1_initial.png b/testing/subagent_generated/e2e/screenshots/step1_initial.png new file mode 100644 index 0000000..80a4d05 Binary files /dev/null and b/testing/subagent_generated/e2e/screenshots/step1_initial.png differ diff --git a/testing/subagent_generated/e2e/screenshots/step2_variety_selected.png b/testing/subagent_generated/e2e/screenshots/step2_variety_selected.png new file mode 100644 index 0000000..0f9c930 Binary files /dev/null and b/testing/subagent_generated/e2e/screenshots/step2_variety_selected.png differ diff --git a/testing/subagent_generated/e2e/screenshots/step3_fert_picker.png b/testing/subagent_generated/e2e/screenshots/step3_fert_picker.png new file mode 100644 index 0000000..1ee3bfa Binary files /dev/null and b/testing/subagent_generated/e2e/screenshots/step3_fert_picker.png differ diff --git a/testing/subagent_generated/e2e/screenshots/step4_with_matrix.png b/testing/subagent_generated/e2e/screenshots/step4_with_matrix.png new file mode 100644 index 0000000..58d5767 Binary files /dev/null and b/testing/subagent_generated/e2e/screenshots/step4_with_matrix.png differ diff --git a/testing/subagent_generated/e2e/screenshots/step5_matrix_table.png b/testing/subagent_generated/e2e/screenshots/step5_matrix_table.png new file mode 100644 index 0000000..d71ae23 Binary files /dev/null and b/testing/subagent_generated/e2e/screenshots/step5_matrix_table.png differ diff --git a/frontend/e2e/verify-fixes.spec.ts b/testing/subagent_generated/e2e/verify-fixes.spec.ts similarity index 100% rename from frontend/e2e/verify-fixes.spec.ts rename to testing/subagent_generated/e2e/verify-fixes.spec.ts diff --git a/testing/subagent_generated/playwright_debug.mjs b/testing/subagent_generated/playwright_debug.mjs new file mode 100644 index 0000000..59521e7 --- /dev/null +++ b/testing/subagent_generated/playwright_debug.mjs @@ -0,0 +1,35 @@ +import { chromium } from 'playwright'; + +const browser = await chromium.launch({ headless: true }); +const context = await browser.newContext({ viewport: { width: 1400, height: 900 } }); +const page = await context.newPage(); + +import { mkdirSync } from 'fs'; +mkdirSync('C:/tmp/playwright_screenshots', { recursive: true }); + +// Navigate to login +await page.goto('http://localhost:3000/login'); +await page.waitForLoadState('networkidle'); +await page.screenshot({ path: 'C:/tmp/playwright_screenshots/login_page.png', fullPage: true }); + +// Debug HTML form +const formHTML = await page.locator('form').innerHTML().catch(() => 'no form found'); +console.log('Form HTML:', formHTML.substring(0, 1000)); + +const allInputs = await page.locator('input').all(); +console.log('Inputs count:', allInputs.length); +for (const input of allInputs) { + const name = await input.getAttribute('name'); + const type = await input.getAttribute('type'); + const placeholder = await input.getAttribute('placeholder'); + console.log(`Input: name=${name}, type=${type}, placeholder=${placeholder}`); +} + +const allButtons = await page.locator('button').all(); +for (const btn of allButtons) { + const text = await btn.textContent(); + const type = await btn.getAttribute('type'); + console.log(`Button: text="${text?.trim()}", type=${type}`); +} + +await browser.close(); diff --git a/testing/subagent_generated/playwright_full_test.mjs b/testing/subagent_generated/playwright_full_test.mjs new file mode 100644 index 0000000..f78db4e --- /dev/null +++ b/testing/subagent_generated/playwright_full_test.mjs @@ -0,0 +1,58 @@ +import { chromium } from 'playwright'; +import { mkdirSync } from 'fs'; + +const screenshotDir = 'C:/tmp/playwright_screenshots'; +mkdirSync(screenshotDir, { recursive: true }); + +const browser = await chromium.launch({ headless: true }); +const context = await browser.newContext({ viewport: { width: 1400, height: 900 } }); +const page = await context.newPage(); + +async function screenshot(name) { + const path = `${screenshotDir}/${name}.png`; + await page.screenshot({ path, fullPage: true }); + console.log(` [Screenshot saved: ${name}.png]`); + return path; +} + +console.log('\n=== Step 1: Navigate to /fertilizer/new ==='); +await page.goto('http://localhost:3000/fertilizer/new'); +await page.waitForLoadState('networkidle'); +console.log('URL:', page.url()); + +if (page.url().includes('/login')) { + console.log('\n=== Step 2: Login ==='); + // Use the id-based selectors since name attribute is null + await page.fill('#username', 'akira'); + await page.fill('input[type="password"]', 'keina2025'); + await screenshot('login_filled'); + await page.click('button[type="submit"]'); + await page.waitForNavigation({ timeout: 10000 }).catch(() => console.log('No navigation event')); + await page.waitForLoadState('networkidle'); + console.log('After login URL:', page.url()); + + // Navigate to fertilizer/new + await page.goto('http://localhost:3000/fertilizer/new'); + await page.waitForLoadState('networkidle'); + console.log('After redirect URL:', page.url()); +} + +await screenshot('step3_fertilizer_new'); +console.log('\n=== Step 3: Page content ==='); +const h1Text = await page.locator('h1, h2').first().textContent().catch(() => 'not found'); +console.log('Heading:', h1Text); + +// List all select elements +const selects = await page.locator('select').all(); +console.log('Select elements:', selects.length); +for (const sel of selects) { + const label = await sel.getAttribute('aria-label') || await sel.getAttribute('id') || 'no label'; + const options = await sel.locator('option').allTextContents(); + console.log(` Select [${label}]: options = ${options.slice(0, 10).join(', ')}`); +} + +// List all visible text to understand the page +const allText = await page.locator('body').textContent(); +console.log('Page text (first 1000 chars):', allText?.substring(0, 1000)); + +await browser.close(); diff --git a/testing/subagent_generated/playwright_full_test2.mjs b/testing/subagent_generated/playwright_full_test2.mjs new file mode 100644 index 0000000..4f178e3 --- /dev/null +++ b/testing/subagent_generated/playwright_full_test2.mjs @@ -0,0 +1,76 @@ +import { chromium } from 'playwright'; +import { mkdirSync } from 'fs'; + +const screenshotDir = 'C:/tmp/playwright_screenshots'; +mkdirSync(screenshotDir, { recursive: true }); + +const browser = await chromium.launch({ headless: true }); +const context = await browser.newContext({ viewport: { width: 1400, height: 900 } }); +const page = await context.newPage(); + +async function screenshot(name) { + const path = `${screenshotDir}/${name}.png`; + await page.screenshot({ path, fullPage: false }); + console.log(` [Screenshot: ${name}.png]`); +} + +// Login +await page.goto('http://localhost:3000/fertilizer/new'); +await page.waitForLoadState('networkidle'); +if (page.url().includes('/login')) { + await page.fill('#username', 'akira'); + await page.fill('input[type="password"]', 'keina2025'); + await page.click('button[type="submit"]'); + await page.waitForNavigation({ timeout: 10000 }).catch(() => {}); + await page.waitForLoadState('networkidle'); + await page.goto('http://localhost:3000/fertilizer/new'); + await page.waitForLoadState('networkidle'); +} + +// Step 3: Select にこまる +console.log('\n=== Step 3: Select 品種 "にこまる" ==='); +const selects = await page.locator('select').all(); +await selects[1].selectOption({ label: 'にこまる' }); +await page.waitForTimeout(1500); +await screenshot('step3_nikkomaru_selected'); +console.log('Selected にこまる - 15 fields auto-added'); + +// Step 4: Click + 肥料を追加 button +console.log('\n=== Step 4: Click "+ 肥料を追加" ==='); +const addBtn = page.locator('button').filter({ hasText: '肥料を追加' }).first(); +await addBtn.click(); +await page.waitForTimeout(500); + +// Modal appeared - click グアノ +console.log(' Modal open - clicking グアノ...'); +const guanoItem = page.locator('text=グアノ').first(); +await guanoItem.click(); +await page.waitForTimeout(1500); +await screenshot('step4_guano_added'); + +// Check what appeared on the page +const pageText = await page.locator('body').textContent(); +const cleanText = pageText?.replace(/\s+/g, ' '); +const relevantPart = cleanText?.match(/自動計算設定.{0,500}/)?.[0] || cleanText?.substring(0, 600); +console.log('After グアノ added:', relevantPart); + +// Find the param input for グアノ +console.log('\n=== Checking fertilizer section structure ==='); +const fertSection = page.locator('[class*="fertilizer"], [class*="Fertilizer"]').first(); +const fertHTML = await page.locator('body').innerHTML(); +// Look for the input near グアノ +const inputs = await page.locator('input[type="number"], input[type="text"]').all(); +console.log('Number of inputs:', inputs.length); +for (let i = 0; i < inputs.length; i++) { + const val = await inputs[i].inputValue(); + const placeholder = await inputs[i].getAttribute('placeholder'); + const type = await inputs[i].getAttribute('type'); + console.log(` Input[${i}]: value="${val}", placeholder="${placeholder}", type="${type}"`); +} + +// Look for 計算 button +const calcButton = page.locator('button').filter({ hasText: '計算' }); +const calcVisible = await calcButton.isVisible(); +console.log('計算 button visible:', calcVisible); + +await browser.close(); diff --git a/testing/subagent_generated/playwright_full_test3.mjs b/testing/subagent_generated/playwright_full_test3.mjs new file mode 100644 index 0000000..6210927 --- /dev/null +++ b/testing/subagent_generated/playwright_full_test3.mjs @@ -0,0 +1,89 @@ +import { chromium } from 'playwright'; +import { mkdirSync } from 'fs'; + +const screenshotDir = 'C:/tmp/playwright_screenshots'; +mkdirSync(screenshotDir, { recursive: true }); + +const browser = await chromium.launch({ headless: true }); +const context = await browser.newContext({ viewport: { width: 1400, height: 900 } }); +const page = await context.newPage(); + +async function screenshot(name) { + const path = `${screenshotDir}/${name}.png`; + await page.screenshot({ path, fullPage: false }); + console.log(` [Screenshot: ${name}.png]`); +} + +// Login +await page.goto('http://localhost:3000/fertilizer/new'); +await page.waitForLoadState('networkidle'); +if (page.url().includes('/login')) { + await page.fill('#username', 'akira'); + await page.fill('input[type="password"]', 'keina2025'); + await page.click('button[type="submit"]'); + await page.waitForNavigation({ timeout: 10000 }).catch(() => {}); + await page.waitForLoadState('networkidle'); + await page.goto('http://localhost:3000/fertilizer/new'); + await page.waitForLoadState('networkidle'); +} + +// Select にこまる +const selects = await page.locator('select').all(); +await selects[1].selectOption({ label: 'にこまる' }); +await page.waitForTimeout(1000); + +// Click + 肥料を追加 +const addBtn = page.locator('button').filter({ hasText: '肥料を追加' }).first(); +await addBtn.click(); +await page.waitForTimeout(500); + +// Click グアノ in modal +await page.locator('text=グアノ').first().click(); +await page.waitForTimeout(1000); + +// Step 5: Enter "3" in the param field and click 計算 +console.log('\n=== Step 5: Enter "3" in param field and click 計算 ==='); + +// The first input with placeholder "値" is the param field +const paramInput = page.locator('input[placeholder="値"]'); +const paramVisible = await paramInput.isVisible(); +console.log('Param input visible:', paramVisible); + +await paramInput.fill('3'); +await page.waitForTimeout(300); + +const calcBtn = page.locator('button').filter({ hasText: '計算' }); +await calcBtn.click(); +await page.waitForTimeout(1500); + +await screenshot('step5_after_calc'); + +// Step 6: Check matrix cells - do they show decimal values in input fields? +console.log('\n=== Step 6: Check matrix cell values BEFORE clicking ≈ ==='); +const allInputs = await page.locator('input[type="number"]').all(); +console.log('Number of number inputs:', allInputs.length); +for (let i = 0; i < Math.min(allInputs.length, 20); i++) { + const val = await allInputs[i].inputValue(); + const placeholder = await allInputs[i].getAttribute('placeholder'); + console.log(` Input[${i}]: value="${val}", placeholder="${placeholder}"`); +} + +// Step 7: Find and examine the グアノ column header button (≈ button) +console.log('\n=== Step 7: Find ≈ button in グアノ column header ==='); +// Look for buttons with ≈ or similar content +const allButtons = await page.locator('button').all(); +console.log('Total buttons on page:', allButtons.length); +for (let i = 0; i < allButtons.length; i++) { + const text = await allButtons[i].textContent(); + const cls = await allButtons[i].getAttribute('class'); + if (text && (text.includes('≈') || text.includes('↩') || text.includes('~') || text.trim().length <= 3)) { + console.log(` Button[${i}]: text="${text?.trim()}", class="${cls?.substring(0, 100)}"`); + } +} + +// Get page text in the グアノ column area +const bodyText = await page.locator('body').textContent(); +const guanoSection = bodyText?.match(/グアノ.{0,300}/)?.[0]; +console.log('グアノ section text:', guanoSection); + +await browser.close(); diff --git a/testing/subagent_generated/playwright_full_test4.mjs b/testing/subagent_generated/playwright_full_test4.mjs new file mode 100644 index 0000000..7062c8a --- /dev/null +++ b/testing/subagent_generated/playwright_full_test4.mjs @@ -0,0 +1,146 @@ +import { chromium } from 'playwright'; +import { mkdirSync } from 'fs'; + +const screenshotDir = 'C:/tmp/playwright_screenshots'; +mkdirSync(screenshotDir, { recursive: true }); + +const browser = await chromium.launch({ headless: true }); +const context = await browser.newContext({ viewport: { width: 1400, height: 900 } }); +const page = await context.newPage(); + +async function screenshot(name) { + const path = `${screenshotDir}/${name}.png`; + await page.screenshot({ path, fullPage: false }); + console.log(` [Screenshot: ${name}.png]`); +} + +// Login +await page.goto('http://localhost:3000/fertilizer/new'); +await page.waitForLoadState('networkidle'); +if (page.url().includes('/login')) { + await page.fill('#username', 'akira'); + await page.fill('input[type="password"]', 'keina2025'); + await page.click('button[type="submit"]'); + await page.waitForNavigation({ timeout: 10000 }).catch(() => {}); + await page.waitForLoadState('networkidle'); + await page.goto('http://localhost:3000/fertilizer/new'); + await page.waitForLoadState('networkidle'); +} + +// Setup: Select にこまる, add グアノ, enter 3 and calculate +const selects = await page.locator('select').all(); +await selects[1].selectOption({ label: 'にこまる' }); +await page.waitForTimeout(1000); +await page.locator('button').filter({ hasText: '肥料を追加' }).first().click(); +await page.waitForTimeout(500); +await page.locator('text=グアノ').first().click(); +await page.waitForTimeout(1000); +await page.locator('input[placeholder="値"]').fill('3'); +await page.locator('button').filter({ hasText: '計算' }).click(); +await page.waitForTimeout(1500); + +// Verify state before ≈ click +console.log('\n=== BEFORE clicking ≈ ==='); +const inputsBefore = await page.locator('input[type="number"]').all(); +console.log('Matrix cell values (before ≈):'); +const valuesBefore = []; +for (let i = 1; i < inputsBefore.length; i++) { + const val = await inputsBefore[i].inputValue(); + valuesBefore.push(val); + console.log(` Cell[${i}]: ${val}`); +} + +// Check the ≈ button +const approxBtn = page.locator('button', { hasText: '≈' }); +const approxBtnClass = await approxBtn.getAttribute('class'); +console.log('\n≈ button class:', approxBtnClass); +const approxBtnText = await approxBtn.textContent(); +console.log('≈ button text:', approxBtnText?.trim()); + +await screenshot('step6_before_approx_click'); + +// Step 8: Click ≈ button +console.log('\n=== Step 8: Click ≈ button ==='); +await approxBtn.click(); +await page.waitForTimeout(1500); + +await screenshot('step9_after_approx_click'); + +// Step 9: Check values after ≈ click +console.log('\n=== Step 9: Check values AFTER clicking ≈ ==='); +const inputsAfter = await page.locator('input[type="number"]').all(); +console.log('Matrix cell values (after ≈):'); +const valuesAfter = []; +for (let i = 1; i < inputsAfter.length; i++) { + const val = await inputsAfter[i].inputValue(); + valuesAfter.push(val); + console.log(` Cell[${i}]: ${val}`); +} + +// Check for reference values (gray text) - might be in span or other elements +// Look for elements that show original calc values as reference +const refValueElements = await page.locator('[class*="gray"], [class*="text-gray"], [class*="ref"]').all(); +console.log('\nLooking for reference value indicators...'); + +// Check all visible text in the table area +const tableText = await page.locator('table, [role="table"], .table-auto, [class*="table"]').first().textContent().catch(() => null); +if (tableText) { + console.log('Table text:', tableText.replace(/\s+/g, ' ').substring(0, 500)); +} else { + // Try to get the section after the header + const bodyText = await page.locator('body').textContent(); + const afterHeader = bodyText?.match(/圃場名.*面積.*グアノ.{0,1000}/s)?.[0]; + console.log('Matrix section:', afterHeader?.replace(/\s+/g, ' ').substring(0, 500)); +} + +// Find the new button (should be ↩ now) +const allButtonsAfter = await page.locator('button').all(); +console.log('\nLooking for ↩ button:'); +for (let i = 0; i < allButtonsAfter.length; i++) { + const text = await allButtonsAfter[i].textContent(); + const cls = await allButtonsAfter[i].getAttribute('class'); + if (text && text.trim().length <= 3 && text.trim() !== '') { + console.log(` Button[${i}]: text="${text?.trim()}", class="${cls?.substring(0, 100)}"`); + } +} + +// Step 10: Click ↩ button +console.log('\n=== Step 10: Click ↩ button ==='); +const restoreBtn = page.locator('button').filter({ hasText: '↩' }); +const restoreBtnVisible = await restoreBtn.isVisible().catch(() => false); +console.log('↩ button visible:', restoreBtnVisible); + +if (restoreBtnVisible) { + const restoreBtnClass = await restoreBtn.getAttribute('class'); + console.log('↩ button class:', restoreBtnClass); + + await restoreBtn.click(); + await page.waitForTimeout(1500); + + await screenshot('step11_after_restore'); + + // Step 11: Check values restored + console.log('\n=== Step 11: Check values AFTER clicking ↩ ==='); + const inputsRestored = await page.locator('input[type="number"]').all(); + console.log('Matrix cell values (after ↩):'); + for (let i = 1; i < inputsRestored.length; i++) { + const val = await inputsRestored[i].inputValue(); + console.log(` Cell[${i}]: ${val}`); + } + + // Check button is back to ≈ + const approxBtnRestored = page.locator('button', { hasText: '≈' }); + const approxVisible = await approxBtnRestored.isVisible().catch(() => false); + const approxClass = await approxBtnRestored.getAttribute('class').catch(() => null); + console.log('\n≈ button visible again:', approxVisible); + console.log('≈ button class:', approxClass); +} else { + console.log('WARNING: ↩ button not found!'); + // Print all button texts for debugging + for (let i = 0; i < allButtonsAfter.length; i++) { + const text = await allButtonsAfter[i].textContent(); + console.log(` Button[${i}]: "${text?.trim()}"`); + } +} + +await browser.close(); diff --git a/testing/subagent_generated/playwright_test_temp.mjs b/testing/subagent_generated/playwright_test_temp.mjs new file mode 100644 index 0000000..e5ce90a --- /dev/null +++ b/testing/subagent_generated/playwright_test_temp.mjs @@ -0,0 +1,66 @@ +import { chromium } from 'playwright'; + +const browser = await chromium.launch({ headless: true }); +const context = await browser.newContext({ viewport: { width: 1400, height: 900 } }); +const page = await context.newPage(); + +const screenshotDir = 'C:/tmp/playwright_screenshots'; +// Create directory via node +import { mkdirSync } from 'fs'; +mkdirSync(screenshotDir, { recursive: true }); + +function screenshot(name) { + return page.screenshot({ path: `${screenshotDir}/${name}.png`, fullPage: true }); +} + +// Step 1: Navigate to fertilizer/new +console.log('\n=== Step 1: Navigating to http://localhost:3000/fertilizer/new ==='); +await page.goto('http://localhost:3000/fertilizer/new'); +await page.waitForLoadState('networkidle'); +console.log('Current URL:', page.url()); + +// Check if redirected to login +if (page.url().includes('/login')) { + console.log('\n=== Step 2: Redirected to login, logging in... ==='); + + // Find and fill login form + await page.waitForSelector('input', { timeout: 5000 }); + const inputs = await page.locator('input').all(); + console.log('Found inputs:', inputs.length); + + // Try different selectors for username/password + try { + await page.fill('input[name="username"]', 'akira'); + } catch { + await page.fill('input[type="text"]:first-of-type', 'akira'); + } + + try { + await page.fill('input[name="password"]', 'keina2025'); + } catch { + await page.fill('input[type="password"]', 'keina2025'); + } + + await screenshot('step2_login_form'); + await page.click('button[type="submit"]'); + await page.waitForLoadState('networkidle'); + console.log('After login URL:', page.url()); + + // Navigate to fertilizer/new after login + await page.goto('http://localhost:3000/fertilizer/new'); + await page.waitForLoadState('networkidle'); + console.log('After navigate URL:', page.url()); +} + +await screenshot('step3_fertilizer_new'); +console.log('\n=== Step 3: On fertilizer/new page ==='); +console.log('URL:', page.url()); + +// Check page content +const pageTitle = await page.title(); +console.log('Page title:', pageTitle); +const bodyText = await page.locator('body').textContent(); +console.log('Body preview:', bodyText?.substring(0, 500)); + +await browser.close(); +console.log('\nDone!'); diff --git a/testing/subagent_generated/playwright_ui_test.mjs b/testing/subagent_generated/playwright_ui_test.mjs new file mode 100644 index 0000000..8a9de13 --- /dev/null +++ b/testing/subagent_generated/playwright_ui_test.mjs @@ -0,0 +1,97 @@ +import { chromium } from 'playwright'; +import { mkdirSync } from 'fs'; + +const screenshotDir = 'C:/tmp/playwright_screenshots'; +mkdirSync(screenshotDir, { recursive: true }); + +const browser = await chromium.launch({ headless: true }); +const context = await browser.newContext({ viewport: { width: 1400, height: 900 } }); +const page = await context.newPage(); + +async function screenshot(name) { + const path = `${screenshotDir}/${name}.png`; + await page.screenshot({ path, fullPage: false }); + console.log(` [Screenshot: ${name}.png]`); +} + +async function screenshotFull(name) { + const path = `${screenshotDir}/${name}.png`; + await page.screenshot({ path, fullPage: true }); + console.log(` [Screenshot (full): ${name}.png]`); +} + +// Login flow +console.log('\n=== Login ==='); +await page.goto('http://localhost:3000/fertilizer/new'); +await page.waitForLoadState('networkidle'); +if (page.url().includes('/login')) { + await page.fill('#username', 'akira'); + await page.fill('input[type="password"]', 'keina2025'); + await page.click('button[type="submit"]'); + await page.waitForNavigation({ timeout: 10000 }).catch(() => {}); + await page.waitForLoadState('networkidle'); + await page.goto('http://localhost:3000/fertilizer/new'); + await page.waitForLoadState('networkidle'); +} +console.log('On page:', page.url()); + +// Step 3: Select variety "にこまる" +console.log('\n=== Step 3: Select 品種 "にこまる" ==='); +// Find the 品種 select (second select - first is year) +const selects = await page.locator('select').all(); +console.log('Number of select elements:', selects.length); +// The variety select should contain "にこまる" +for (let i = 0; i < selects.length; i++) { + const options = await selects[i].locator('option').allTextContents(); + console.log(`Select[${i}] options:`, options.join(', ')); +} + +// Select にこまる +await selects[1].selectOption({ label: 'にこまる' }); +await page.waitForTimeout(2000); // Wait for any async updates +await screenshotFull('step3_variety_selected'); +console.log('Selected にこまる'); + +// Check if any fields were auto-added +const allText = await page.locator('body').textContent(); +const relevantText = allText?.replace(/\s+/g, ' ').substring(0, 500); +console.log('Page text after variety select:', relevantText); + +// Step 4: Add fertilizer by clicking "+ 肥料を追加" +console.log('\n=== Step 4: Click "+ 肥料を追加" ==='); +// Find the button +const addBtn = page.locator('button').filter({ hasText: '肥料を追加' }).first(); +const addBtnVisible = await addBtn.isVisible(); +console.log('Add fertilizer button visible:', addBtnVisible); + +if (addBtnVisible) { + await addBtn.click(); + await page.waitForTimeout(1000); + await screenshotFull('step4_after_add_click'); + + // Now we need to select "グアノ" from the new fertilizer row + const allText2 = await page.locator('body').textContent(); + console.log('Page text after click:', allText2?.replace(/\s+/g, ' ').substring(0, 300)); + + // Check for any new select/dropdown that appeared + const newSelects = await page.locator('select').all(); + console.log('Selects after click:', newSelects.length); + + for (let i = 0; i < newSelects.length; i++) { + const options = await newSelects[i].locator('option').allTextContents(); + if (options.some(o => o.includes('グアノ'))) { + console.log(`Found グアノ in select[${i}], selecting...`); + await newSelects[i].selectOption({ label: 'グアノ' }); + break; + } + } +} + +await page.waitForTimeout(2000); +await screenshotFull('step4_guano_selected'); + +// Check page state +const pageBody = await page.locator('body').textContent(); +console.log('After グアノ selection:', pageBody?.replace(/\s+/g, ' ').substring(0, 500)); + +await browser.close(); diff --git a/testing/subagent_generated/test-results/.last-run.json b/testing/subagent_generated/test-results/.last-run.json new file mode 100644 index 0000000..cbcc1fb --- /dev/null +++ b/testing/subagent_generated/test-results/.last-run.json @@ -0,0 +1,4 @@ +{ + "status": "passed", + "failedTests": [] +} \ No newline at end of file diff --git a/testing/subagent_generated/verify-fixes.spec.ts b/testing/subagent_generated/verify-fixes.spec.ts new file mode 100644 index 0000000..39fa355 --- /dev/null +++ b/testing/subagent_generated/verify-fixes.spec.ts @@ -0,0 +1,199 @@ +import { test, expect, Page } from '@playwright/test'; + +const LOGIN_URL = 'http://localhost:3000/login'; +const API_URL = 'http://localhost:8000/api'; +const USERNAME = 'admin'; +const PASSWORD = 'admin123'; + +/** ログインしてトークンを localStorage にセット */ +async function loginViaUI(page: Page) { + await page.goto(LOGIN_URL); + await page.fill('#username', USERNAME); + await page.fill('#password', PASSWORD); + await page.click('button[type="submit"]'); + // ログイン後 /allocation にリダイレクトされるのを待つ + await page.waitForURL('**/allocation', { timeout: 15000 }); +} + +// ===== D-4: IsAuthenticated が有効か ===== +test.describe('D-4: IsAuthenticated', () => { + test('未認証で API にアクセスすると 401 が返る', async ({ request }) => { + const res = await request.get(`${API_URL}/fields/`); + expect(res.status()).toBe(401); + }); + + test('未認証でフロントにアクセスするとログイン画面にリダイレクト', async ({ page }) => { + await page.goto('http://localhost:3000/allocation'); + // API が 401 を返すので、フロントがログイン画面に遷移するはず + await page.waitForURL('**/login', { timeout: 10000 }); + await expect(page.locator('text=KeinaSystem')).toBeVisible(); + }); +}); + +// ===== ログイン → 作付計画画面 ===== +test.describe('ログイン → 作付計画画面', () => { + test('ログインして作付計画画面が表示される', async ({ page }) => { + await loginViaUI(page); + // allocation ページにいることを確認 + expect(page.url()).toContain('/allocation'); + // 圃場のデータが読み込まれるのを待つ(テーブルまたはリストが表示されるはず) + await page.waitForTimeout(2000); + // ページ内に何かしらのコンテンツがある + const body = await page.textContent('body'); + expect(body).toBeTruthy(); + }); +}); + +// ===== A-8: 圃場詳細の共済/中山間情報表示 ===== +test.describe('A-8: 圃場詳細 共済/中山間情報', () => { + test('共済・中山間の両方が紐づく圃場で情報が表示される', async ({ page }) => { + await loginViaUI(page); + // Field ID 8 (口神 ハウス南) は kyosai=1, chusankan=1 + await page.goto('http://localhost:3000/fields/8'); + await page.waitForLoadState('networkidle'); + + // 共済情報セクション + await expect(page.locator('text=共済情報')).toBeVisible(); + // テーブルヘッダが表示される + await expect(page.locator('text=耕地-分筆')).toBeVisible(); + await expect(page.locator('text=漢字地名')).toBeVisible(); + // 「紐づけられた共済区画はありません」が表示されないこと + await expect(page.locator('text=紐づけられた共済区画はありません')).not.toBeVisible(); + + // 中山間情報セクション + await expect(page.locator('text=中山間情報')).toBeVisible(); + await expect(page.locator('text=所在地')).toBeVisible(); + // 「紐づけられた中山間区画はありません」が表示されないこと + await expect(page.locator('text=紐づけられた中山間区画はありません')).not.toBeVisible(); + }); + + test('共済のみ紐づく圃場で共済情報が表示される', async ({ page }) => { + await loginViaUI(page); + // Field ID 3 (おまけ) は kyosai=1, chusankan=0 + await page.goto('http://localhost:3000/fields/3'); + await page.waitForLoadState('networkidle'); + + await expect(page.locator('text=共済情報')).toBeVisible(); + await expect(page.locator('text=紐づけられた共済区画はありません')).not.toBeVisible(); + await expect(page.locator('text=紐づけられた中山間区画はありません')).toBeVisible(); + }); +}); + +// ===== E-1: PDF帳票フォーマット再設計 ===== +test.describe('E-1: PDF帳票', () => { + test('共済PDF が 200 で返る(表形式テンプレート)', async ({ page }) => { + await loginViaUI(page); + const token = await page.evaluate(() => localStorage.getItem('accessToken')); + const res = await page.request.get(`${API_URL}/reports/kyosai/2025/`, { + headers: { Authorization: `Bearer ${token}` }, + }); + expect(res.status()).toBe(200); + expect(res.headers()['content-type']).toContain('application/pdf'); + }); + + test('中山間PDF が 200 で返る(表形式テンプレート)', async ({ page }) => { + await loginViaUI(page); + const token = await page.evaluate(() => localStorage.getItem('accessToken')); + const res = await page.request.get(`${API_URL}/reports/chusankan/2025/`, { + headers: { Authorization: `Bearer ${token}` }, + }); + expect(res.status()).toBe(200); + expect(res.headers()['content-type']).toContain('application/pdf'); + }); + + test('帳票画面からPDFダウンロードリンクが表示される', async ({ page }) => { + await loginViaUI(page); + await page.goto('http://localhost:3000/reports'); + await page.waitForLoadState('networkidle'); + // 帳票画面が表示される + const body = await page.textContent('body'); + expect(body).toBeTruthy(); + }); +}); + +// ===== E-1c: 中山間マスタ 17フィールド ===== +test.describe('E-1c: 中山間マスタ拡張', () => { + test('中山間 API が 17 フィールドを返す', async ({ page }) => { + await loginViaUI(page); + const token = await page.evaluate(() => localStorage.getItem('accessToken')); + // Field ID 8 は chusankan=1 + const res = await page.request.get(`${API_URL}/fields/8/`, { + headers: { Authorization: `Bearer ${token}` }, + }); + expect(res.status()).toBe(200); + const data = await res.json(); + const ch = data.chusankan_fields[0]; + expect(ch).toBeDefined(); + // 新フィールドが存在する + expect(ch).toHaveProperty('manager'); + expect(ch).toHaveProperty('owner'); + expect(ch).toHaveProperty('planting_area'); + expect(ch).toHaveProperty('original_crop'); + expect(ch).toHaveProperty('slope'); + expect(ch).toHaveProperty('base_amount'); + expect(ch).toHaveProperty('branch_num'); + expect(ch).toHaveProperty('land_type'); + }); + + test('共済マスタの面積が 0 でない', async ({ page }) => { + await loginViaUI(page); + const token = await page.evaluate(() => localStorage.getItem('accessToken')); + // Field ID 3 (おまけ) は kyosai=1 + const res = await page.request.get(`${API_URL}/fields/3/`, { + headers: { Authorization: `Bearer ${token}` }, + }); + const data = await res.json(); + const k = data.kyosai_fields[0]; + expect(k).toBeDefined(); + expect(k.area).toBeGreaterThan(0); + }); +}); + +// ===== C-2: 共済マスタ unique 制約 ===== +test.describe('C-2: 共済マスタ unique 制約 (k_num, s_num)', () => { + test('同じ k_num + s_num の重複登録が拒否される', async ({ page }) => { + await loginViaUI(page); + const token = await page.evaluate(() => localStorage.getItem('accessToken')); + + // まずテスト用データを作成 + const createRes = await page.request.post(`${API_URL}/fields/kyosai/`, { + headers: { + Authorization: `Bearer ${token}`, + 'Content-Type': 'application/json', + }, + data: { + k_num: 'TEST99', + s_num: '1', + kanji_name: 'テスト区画', + address: 'テスト住所', + area: 1000, + }, + }); + + // 作成成功 or 既に存在する場合 + if (createRes.status() === 201) { + // 同じ k_num + s_num で再度作成 → 拒否されるべき + const dupRes = await page.request.post(`${API_URL}/fields/kyosai/`, { + headers: { + Authorization: `Bearer ${token}`, + 'Content-Type': 'application/json', + }, + data: { + k_num: 'TEST99', + s_num: '1', + kanji_name: 'テスト区画2', + address: 'テスト住所2', + area: 2000, + }, + }); + // 400 (validation error) が返るべき + expect(dupRes.status()).toBe(400); + + // クリーンアップ: テストデータ削除 + const created = await createRes.json(); + await page.request.delete(`${API_URL}/fields/kyosai/${created.id}/`, { + headers: { Authorization: `Bearer ${token}` }, + }); + } + }); +}); diff --git a/testing/test_report.md b/testing/test_report.md new file mode 100644 index 0000000..dfb1464 --- /dev/null +++ b/testing/test_report.md @@ -0,0 +1,159 @@ +# KeinaSystem 読み取り専用テスト結果レポート + +> **実施日**: 2026-03-01 +> **テスト方針**: データの書き込みを一切行わず、既存データの範囲内で画面表示・操作を検証 + +--- + +## テスト結果サマリー + +| テスト対象 | 結果 | 備考 | +|---|---|---| +| ログイン | ✅ 正常 | ログイン後 `/allocation` にリダイレクト | +| ダッシュボード | ✅ 正常 | サマリーカード・集計テーブル・クイックアクションすべて表示OK | +| 圃場一覧 | ✅ 正常 | 39筆表示、並び替え・対応表切り替え動作OK | +| 圃場詳細 | ✅ 正常 | 基本情報・共済1件・中山間1件の紐付き表示OK | +| 圃場詳細(存在しないID) | ✅ 正常 | 「圃場が見つかりません」の適切なエラー表示 | +| 対応表モード | ✅ 正常 | 共済/中山間の紐付き一覧が正しく表示 | +| 作付け計画 | ✅ 正常 | 年度セレクタ・ソート・集計サイドバーすべて動作OK | +| 作付け計画 フィルタ | ✅ 正常 | グループ順ソートが正しく動作 | +| 帳票出力画面 | ✅ 正常 | 2種類の帳票表示OK | +| 共済細目書PDFプレビュー | ⚠️ 軽微な問題 | 下記「発見事項」参照 | +| 中山間申請書PDFプレビュー | ⚠️ 軽微な問題 | 下記「発見事項」参照 | +| データ取込画面 | ✅ 正常 | 3種類のインポートセクション表示OK | +| 気象データ(年別集計) | ✅ 正常 | サマリーカード・グラフ正常表示 | +| 気象データ(月別サマリー) | ⚠️ 軽微な問題 | 下記「発見事項」参照 | +| 気象データ(直近14日) | ✅ 正常 | 日次データテーブル正常表示 | +| 気象データ(期間指定) | ✅ 正常 | 日付入力フィールド正常表示 | +| 施肥計画一覧 | ✅ 正常 | 2026年度データなし → 「施肥計画はありません」表示OK | +| 肥料マスタ | ✅ 正常 | 5件の肥料レコード正常表示 | +| メール処理履歴 | ✅ 正常 | メール一覧・フィルタ・バッジ表示すべてOK | +| メール通知ルール | ✅ 正常 | 6件のルール正常表示 | + +--- + +## 発見された不具合・改善点 + +### 🟡 問題1: 気象データ月別サマリーのデータ未取得月の表示 + +**画面**: `/weather` → 月別サマリータブ +**現象**: 2026年3月〜12月(まだデータがない月)で、最高気温・最低気温の列に「—°C」と表示される。 +**期待動作**: 「—」のみ表示するか、セルを空にすべき(「°C」単位がつくと0℃と紛らわしい)。 +**重要度**: 低(表示上の問題、データ自体は正しい) + +**スクリーンショット**: `testing/screenshots/06_weather/weather_monthly_summary_*.png` + +--- + +### 🟡 問題2: 水稲共済細目書PDFの紐付けなし行 + +**画面**: `/reports` → 水稲共済細目書プレビュー +**現象**: 耕地番号 2-1 の行で、作付品目・品種・圃場名称がすべて「─」(ダッシュ)になっている。 +**原因の可能性**: + - 共済マスタ耕地 2-1 に実圃場が紐付いていない + - または紐付いてはいるが作付け計画が未設定 +**影響**: PDFの出力内容に空行が含まれる +**重要度**: 中(実運用で提出するPDFに影響する可能性あり。ただし紐付けデータの問題であってシステムの不具合ではないかもしれない) + +**スクリーンショット**: `testing/screenshots/04_reports/report_preview_pdf_*.png` + +--- + +### 🟡 問題3: 中山間交付金申請書PDFの作物・品種・圃場名称 + +**画面**: `/reports` → 中山間交付金申請書プレビュー +**現象**: ほぼすべての行で「作物」「品種」「圃場名称」の列が「─」(空)になっている。 +**原因の可能性**: + - 中山間マスタの区画に実圃場が紐付いていない + - 紐付いた圃場に2026年度の作付け計画がない + - 中山間マスタは71区画あるが実圃場は39筆で、多くの区画が未紐付けの可能性 +**重要度**: 中(問題2と同じ原因の可能性が高い) + +**スクリーンショット**: `testing/screenshots/04_reports/chusankan_report_preview_jap_*.png` + +--- + +### 🔵 情報: コンソール警告 + +**全画面共通**: Next.js の Hydration 警告が出力されるケースがある。これは開発環境特有のもので、ブラウザ拡張機能による属性不一致が原因。システムの動作に影響なし。 + +**favicon.ico**: ダッシュボードで `favicon.ico` の 404 エラーがコンソールに記録されている。 +**重要度**: 最低(ユーザーに影響なし。本番環境でfaviconを設置すれば解消) + +--- + +## 読み取り専用のため実施できなかったテスト一覧 + +以下のテストはデータの書き込みが必要なため、今回のテスト方針では実施不可でした。 + +### 圃場管理 +- [ ] 圃場の新規作成(`POST /api/fields/`) +- [ ] 圃場情報の編集・保存(`PATCH /api/fields/{id}/`) +- [ ] 圃場の削除(`DELETE /api/fields/{id}/`) +- [ ] 共済マスタの紐付け追加(`POST /api/fields/{id}/kyosai-links/`) +- [ ] 共済マスタの紐付け削除 +- [ ] 中山間マスタの紐付け追加・削除 + +### データ取込 +- [ ] 共済マスタODSファイルのインポート +- [ ] 中山間マスタODSファイルのインポート +- [ ] 実圃場ODSファイルのインポート + +### 作付け計画 +- [ ] 作付け計画の作成(作物・品種の設定) +- [ ] 作物のフィルタ(ドロップダウン変更後の表示確認) +- [ ] 一括更新(チェックボックス選択→一括設定) +- [ ] 前年度コピー +- [ ] グループ名のインライン編集 +- [ ] 表示順の変更(↑↓ボタン) +- [ ] 品種管理画面での追加・削除 + +### 帳票出力 +- [ ] PDFダウンロード(ダウンロードボタンのクリック) +- [ ] CSVエクスポート(`GET /api/fields/export/zip/`) + +### 施肥計画 +- [ ] 施肥計画の新規作成 +- [ ] 施肥計画の編集(マトリクス表入力) +- [ ] 自動計算(per_tan / even / nitrogen の3方式) +- [ ] 施肥計画PDF出力 +- [ ] 肥料マスタの新規追加・編集・削除 + +### メール通知 +- [ ] 送信者ルールの追加 +- [ ] 送信者ルールの削除 +- [ ] フィードバックの送信 +- [ ] メール履歴画面からのフィードバック編集 + +### 気象データ +- [ ] 期間指定モードでの日付入力→表示(APIへのリクエスト発生) +- [ ] 過去年度の選択(年セレクタで2016〜2025を選択) + +--- + +## テスト環境 + +- **URL**: http://localhost:3000 +- **認証**: JWT(akira / keina2025) +- **Docker**: docker-compose.yml(db + backend + frontend) +- **テスト日時**: 2026-03-01 12:30〜13:15 + +--- + +## ファイル構成 + +``` +testing/ +├── test_report.md ← このファイル +├── screenshots/ +│ ├── 01_dashboard/ ← ダッシュボード画面 +│ ├── 02_fields/ ← 圃場一覧・詳細・対応表 +│ ├── 03_allocation/ ← 作付け計画 +│ ├── 04_reports/ ← 帳票出力・PDFプレビュー +│ ├── 05_import/ ← データ取込 +│ ├── 06_weather/ ← 気象データ +│ ├── 07_fertilizer/ ← 施肥計画・肥料マスタ +│ └── 08_mail/ ← メール履歴・ルール +├── recordings/ ← ブラウザ操作の録画(WebP) +└── subagent_generated/ ← テスト中に自動生成された一時ファイル +```