HR application
Three Forms modules on Oracle's HR sample schema: LOGIN, CONSOLE and EMPLOYEES (master-detail blocks on tab pages, three lists of values, alerts, calls between forms, APEX links signed with a token).
The live demos share one database per app: visitors see each other's changes, and the data resets to the scenario fixture every night.
Log in as alice / alice#123 (administrator) or bob / bob#123.
Original Forms source: github.com/astramieri/hr-app-forms
at commit 5d6c227, MIT, copyright 2024 Angelo Stramieri licence.
Parity: 17 of 17 scenarios
Each scenario was recorded on the original and replayed against the migrated app. Three runs from a fresh database gave byte-identical diffs for every scenario; none has a defect. Open a scenario for its steps, screenshots of both apps, the golden record, the diff and its waivers.
| Scenario | What it does | match | tolerated | waived | defect | unmapped | Three runs |
|---|---|---|---|---|---|---|---|
| console-apex | The APEX button composes a URL with a signed token (COMPOSE_URL), shows it and calls WEB.SHOW_DOCUMENT. | 39 | 1 | 3 | 0 | 0 | identical |
| console-logout | Log in as bob; LOGOUT erases the global and returns to LOGIN with NEW_FORM. | 44 | 0 | 0 | 0 | 0 | identical |
| employees-apex | The block's APEX button and a row's Details button each compose an APEX URL with a signed token; the client host has no browser, so WEB.SHOW_DOCUMENT answers with FRM-92022. Closing EMPLOYEES ends its database session (OPEN_FORM with SESSION), which writes that session's PL/SQL coverage. | 656 | 4 | 10 | 0 | 0 | identical |
| employees-change-department | Move an employee to another department through the DEPARTMENTS LOV and raise another employee's salary, then save. UPDATE_JOB_HISTORY records the old department for the first row and finds nothing to record for the second (Forms updates every column, so the trigger fires for both). Closing EMPLOYEES ends its database session, which writes that session's PL/SQL coverage. | 551 | 1 | 0 | 0 | 0 | identical |
| employees-change-job | Change an employee's job through the JOBS LOV and save; the UPDATE_JOB_HISTORY trigger records the old job. Closing EMPLOYEES ends its database session, which writes that session's PL/SQL coverage. | 551 | 1 | 0 | 0 | 0 | identical |
| employees-clear | Clear All with no change does nothing; after an edit CONFIRM_CLEAR asks, Cancel keeps it and OK discards it. | 921 | 0 | 0 | 0 | 0 | identical |
| employees-close | Closing EMPLOYEES with an unsaved change asks to commit (EXIT_FORM ASK_COMMIT); No returns to CONSOLE, whose close ends the session. | 429 | 0 | 0 | 0 | 0 | identical |
| employees-create | Create an employee with the three LOVs and save; PRE-INSERT takes the id from EMPLOYEES_SEQ. | 1046 | 3 | 7 | 0 | 0 | identical |
| employees-delete | Delete an employee without history after confirming CONFIRM_DELETE; DELETE_RECORD and COMMIT_FORM. | 552 | 0 | 0 | 0 | 0 | identical |
| employees-delete-refused | Deleting an employee with job history is refused by ON-CHECK-DELETE-MASTER; a cancelled delete changes nothing. | 798 | 0 | 0 | 0 | 0 | identical |
| employees-details | Move to another employee (the relations clear and requery the details) and show each tab page; Save with nothing changed does not commit. | 798 | 0 | 0 | 0 | 0 | identical |
| employees-open | Log in as alice and open EMPLOYEES from CONSOLE; WHEN-NEW-FORM-INSTANCE queries the employees. | 161 | 0 | 0 | 0 | 0 | identical |
| employees-search | Search employees by a name fragment (the first hit has no department, so its DEPARTMENT detail is not queried), Reset back to the full list, then Search with an empty field. | 660 | 0 | 1 | 0 | 0 | identical |
| login-close | Closing the LOGIN window (title-bar close box) runs WHEN-WINDOW-CLOSED, which ends the session. | 26 | 0 | 0 | 0 | 0 | identical |
| login-fail | A wrong password raises ALERT_FAILURE (NO_DATA_FOUND in LOGIN.LOGIN); LOGIN stays open. | 50 | 0 | 0 | 0 | 0 | identical |
| login-ok | Log in as alice; LOGIN opens CONSOLE with NEW_FORM, which shows the user name. | 36 | 0 | 0 | 0 | 0 | identical |
| login-open | Open LOGIN and record its initial state (the prefix of the LOGIN locator check). | 20 | 0 | 0 | 0 | 0 | identical |
| total | 7338 | 10 | 21 | 0 | 0 |
Tolerance policy: tolerance-policy.yaml.
Coverage of the original
Every trigger and program unit of the Forms modules, measured with Forms Runtime Diagnostics while the
scenarios ran; database PL/SQL measured with DBMS_PLSQL_CODE_COVERAGE.
| Module | Triggers | Program units | Covered | Unreachable | Blocked |
|---|---|---|---|---|---|
| CONSOLE | 5 | 0 | 5 | 0 | 0 |
| EMPLOYEES | 14 | 3 | 17 | 0 | 0 |
| LOGIN | 2 | 0 | 2 | 0 | 0 |
| total | 21 | 3 | 24 | 0 | 0 |
Decision branches: 32, of which 24 covered, 4 unreachable (retirement candidates) and 4 blocked. Database PL/SQL: 11 unit(s) in the coverage report, 13 of 13 blocks covered. Exit check: PASS.
Every branch with its class
| Trigger or unit | Branch | Class | Scenarios or reason |
|---|---|---|---|
LOGIN/LOGIN.LOGIN/WHEN-BUTTON-PRESSED | user and password match - console opens | covered | console-apex, console-logout, employees-apex, employees-change-department, employees-change-job, employees-clear, employees-close, employees-create, employees-delete, employees-delete-refused, employees-details, employees-open, employees-search, login-ok |
LOGIN/LOGIN.LOGIN/WHEN-BUTTON-PRESSED | no matching user - ALERT_FAILURE | covered | login-fail |
CONSOLE/CONSOLE.EMPLOYEES/WHEN-BUTTON-PRESSED | EMPLOYEES not open - OPEN_FORM | covered | employees-apex, employees-change-department, employees-change-job, employees-clear, employees-close, employees-create, employees-delete, employees-delete-refused, employees-details, employees-open, employees-search |
CONSOLE/CONSOLE.EMPLOYEES/WHEN-BUTTON-PRESSED | EMPLOYEES already open - GO_FORM | blocked | Needs the CONSOLE button pressed while EMPLOYEES is open. OPEN_FORM(ACTIVATE) puts the EMPLOYEES window over the console, and the way back is the FSAL "Window" menu, which is a client menu with no Forms name (LWMenu1); the runner only clicks named elements. Harness limit. |
EMPLOYEES/BUTTONS.SAVE_BUTTON/WHEN-BUTTON-PRESSED | block changed - commit | covered | employees-change-department, employees-change-job, employees-create |
EMPLOYEES/BUTTONS.SAVE_BUTTON/WHEN-BUTTON-PRESSED | nothing changed - no commit | covered | employees-details |
EMPLOYEES/BUTTONS.DELETE_BUTTON/WHEN-BUTTON-PRESSED | confirmed - delete and commit | covered | employees-delete |
EMPLOYEES/BUTTONS.DELETE_BUTTON/WHEN-BUTTON-PRESSED | cancelled - nothing deleted | covered | employees-delete-refused |
EMPLOYEES/BUTTONS.CLEAR_BUTTON/WHEN-BUTTON-PRESSED | changed, confirmed - changes discarded and requeried | covered | employees-clear |
EMPLOYEES/BUTTONS.CLEAR_BUTTON/WHEN-BUTTON-PRESSED | changed, cancelled - changes kept | covered | employees-clear |
EMPLOYEES/BUTTONS.CLEAR_BUTTON/WHEN-BUTTON-PRESSED | nothing changed - no question | covered | employees-clear |
EMPLOYEES/EMPLOYEES/ON-CHECK-DELETE-MASTER | job history exists, delete refused | covered | employees-delete-refused |
EMPLOYEES/EMPLOYEES/ON-CHECK-DELETE-MASTER | no job history, delete goes ahead | covered | employees-delete |
EMPLOYEES/EMPLOYEES/ON-POPULATE-DETAILS | master record NEW or INSERT - no detail query | covered | employees-clear, employees-create |
EMPLOYEES/EMPLOYEES/ON-POPULATE-DETAILS | DEPARTMENT_ID set - department queried | covered | employees-apex, employees-change-department, employees-change-job, employees-clear, employees-close, employees-create, employees-delete, employees-delete-refused, employees-details, employees-open, employees-search |
EMPLOYEES/EMPLOYEES/ON-POPULATE-DETAILS | DEPARTMENT_ID null - no department query | covered | employees-search |
EMPLOYEES/EMPLOYEES/ON-POPULATE-DETAILS | MANAGER_ID set - manager queried | covered | employees-apex, employees-change-department, employees-change-job, employees-clear, employees-close, employees-delete, employees-delete-refused, employees-details, employees-search |
EMPLOYEES/EMPLOYEES/ON-POPULATE-DETAILS | MANAGER_ID null - no manager query | covered | employees-apex, employees-change-department, employees-change-job, employees-clear, employees-close, employees-create, employees-delete, employees-delete-refused, employees-details, employees-open, employees-search |
EMPLOYEES/EMPLOYEES/ON-POPULATE-DETAILS | EMPLOYEE_ID set - history queried | covered | employees-apex, employees-change-department, employees-change-job, employees-clear, employees-close, employees-create, employees-delete, employees-delete-refused, employees-details, employees-open, employees-search |
EMPLOYEES/EMPLOYEES/ON-POPULATE-DETAILS | EMPLOYEE_ID null on a queried record - no history query | unreachable | EMPLOYEE_ID is the primary key of EMPLOYEES, so a queried record always has it; NEW and INSERT records return before this point. |
EMPLOYEES/EMPLOYEES/ON-POPULATE-DETAILS | cursor moved by the queries, returned to the start item | covered | employees-apex, employees-change-department, employees-change-job, employees-clear, employees-close, employees-create, employees-delete, employees-delete-refused, employees-details, employees-open, employees-search |
EMPLOYEES/EMPLOYEES/ON-POPULATE-DETAILS | cursor still on the start item after the queries | unreachable | Every relation is not deferred, so each detail query does GO_BLOCK to the detail and the cursor always leaves the master item; HISTORY is always queried (EMPLOYEE_ID is the key). |
EMPLOYEES/unit:QUERY_MASTER_DETAILS | relation not deferred - GO_BLOCK and EXECUTE_QUERY | covered | employees-apex, employees-change-department, employees-change-job, employees-clear, employees-close, employees-create, employees-delete, employees-delete-refused, employees-details, employees-open, employees-search |
EMPLOYEES/unit:QUERY_MASTER_DETAILS | relation deferred - SET_BLOCK_PROPERTY non-coordinated | unreachable | No relation of EMPLOYEES is deferred and nothing changes that at run time. |
EMPLOYEES/unit:QUERY_MASTER_DETAILS | GO_BLOCK to the detail failed - exception handler | blocked | Needs GO_BLOCK to a detail block to fail during coordination. The detail blocks have no PRE-BLOCK, WHEN-NEW-BLOCK-INSTANCE or validation that can refuse entry; no input found that makes it fail. |
EMPLOYEES/unit:CHECK_PACKAGE_FAILURE | previous built-in succeeded | covered | employees-apex, employees-change-department, employees-change-job, employees-clear, employees-close, employees-create, employees-delete, employees-delete-refused, employees-details, employees-open, employees-search |
EMPLOYEES/unit:CHECK_PACKAGE_FAILURE | previous built-in failed - raise FORM_TRIGGER_FAILURE | blocked | Called after GO_BLOCK and GO_ITEM in the coordination units; no input found that makes these fail (see the QUERY_MASTER_DETAILS exception branch). |
EMPLOYEES/unit:CLEAR_ALL_MASTER_DETAILS | a detail block holds rows - cleared without validation | covered | employees-apex, employees-change-department, employees-change-job, employees-clear, employees-close, employees-create, employees-delete, employees-delete-refused, employees-details, employees-search |
EMPLOYEES/unit:CLEAR_ALL_MASTER_DETAILS | every detail block empty - nothing cleared | covered | employees-apex, employees-change-department, employees-change-job, employees-clear, employees-close, employees-create, employees-delete, employees-delete-refused, employees-details, employees-open, employees-search |
EMPLOYEES/unit:CLEAR_ALL_MASTER_DETAILS | cursor moved by the clearing, returned to the start item | covered | employees-apex, employees-change-department, employees-change-job, employees-clear, employees-close, employees-create, employees-delete, employees-delete-refused, employees-details, employees-search |
EMPLOYEES/unit:CLEAR_ALL_MASTER_DETAILS | changed detail block below the master - ask to commit | unreachable | DEPARTMENT, MANAGER and HISTORY allow no insert, update or delete, so a detail block is never CHANGED. |
EMPLOYEES/unit:CLEAR_ALL_MASTER_DETAILS | a detail block not cleared - raise FORM_TRIGGER_FAILURE | blocked | Needs CLEAR_BLOCK(NO_VALIDATE) on a read-only detail block to leave rows behind; no input found that makes it fail. |
Database PL/SQL units
| Unit | Type | Blocks | Covered | Class |
|---|---|---|---|---|
| COMPOSE_URL | FUNCTION | 3 | 3 | covered |
| GENERATE_TOKEN | FUNCTION | 5 | 5 | covered |
| VALIDATE_TOKEN | FUNCTION | None | 0 | unreachable |
| ADD_JOB_HISTORY | PROCEDURE | 1 | 1 | covered |
| INSERT_LOG | PROCEDURE | None | 0 | unreachable |
| SECURE_DML | PROCEDURE | None | 0 | unreachable |
| APEX_TRG_01 | TRIGGER | None | 0 | unreachable |
| REQUESTS_TRG_01 | TRIGGER | None | 0 | unreachable |
| SECURE_EMPLOYEES | TRIGGER | None | 0 | unreachable |
| UPDATE_JOB_HISTORY | TRIGGER | 4 | 4 | covered |
| USERS_TRG_01 | TRIGGER | None | 0 | unreachable |
Waivers
Source behaviour a customer would probably not replicate, proposed for the customer's decision. Each is pinned to the exact values it covers; the counts are values waived in the final diffs. File: waivers.yaml.
| Id | Waiver | Values | Reason |
|---|---|---|---|
| H1 | APEX links fail with FRM-92022 instead of opening the page | 13 | The APEX buttons (CONSOLE.APEX, BUTTONS.APEX_BUTTON, the EMPLOYEES.APEX row button) compose a signed APEX URL, show it on the message line and call WEB.SHOW_DOCUMENT(url, '_blank'). On the Forms 14.1.2 client this fails: Forms shows the alert "FRM-92022: cannot launch URL ... The BROWSE action is not supported on the current platform!" and the page never opens, so the source records an alert, alert focus and an extra window, and the user has to dismiss it. The target writes the same message and opens the URL in a new browser tab, which is what the code asks for. Replicating the failure would mean shipping a broken link. The message line (APEX URL with the token) is still compared. |
| H2 | Department tab keeps the previous department number | 8 | When the current employee has no department (a new record, or Kimberely Grant, whose DEPARTMENT_ID is NULL), Forms clears the DEPARTMENT detail block, yet DEPARTMENT_ID on the Department tab keeps showing the number of the department shown before (90, Executive) while name, address, city and region are blank. The number belongs to another employee's department; the target clears the whole tab. |