FormsRefresh

Home / Demo

Student forms

Three Forms modules on a course and student schema: QAFE_FORM1 (courses), QAFE_FORM2 (instructors with their courses, sections and students in four windows, a list of values) and QAFE_FORM3 (courses with counts, sections in a second window, validation triggers).

Open the live migrated app Same app, Angular front end Scenarios and diffs Faithful to the original

Target: .NET 10 and PostgreSQL, the forms' windows, blocks, list of values and alerts in the browser. Original Forms source: github.com/madilraza/oracle-forms-examples at commit 6b10ffc, licence: Apache-2.0.

The live demos share one database per app: visitors see each other's changes, and the data resets to the scenario fixture every night.

Parity: 15 of 15 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.

ScenarioWhat it doesmatchtoleratedwaiveddefectunmappedThree runs
form1-browseBrowse the courses of QAFE_FORM1: WHEN-NEW-FORM-INSTANCE sets :GLOBAL.USER_ID and queries COURSE; the smartbar's Next Record and Previous Record move through the single-record block.590300identical
form1-insertInsert a course: PRE-INSERT takes COURSE_NO from COURSE_NO_SEQ and sets CREATED_DATE and MODIFIED_DATE to SYSDATE (the run date, masked) and the user columns to :GLOBAL.USER_ID ('Qafe').682100identical
form1-updateChange the first course's description and save: PRE-UPDATE sets MODIFIED_DATE to SYSDATE and MODIFIED_BY to :GLOBAL.USER_ID.501000identical
form2-course-delete-refusedRemove Record on a course. FORM2's COURSE block has Delete Allowed No, so Forms refuses with FRM-41049 before the master-detail check (ON-CHECK-DELETE-MASTER) could run.3600000identical
form2-coursesShow Courses queries COURSE and shows the Courses window; choosing another course coordinates its sections (ON-POPULATE-DETAILS, QUERY_MASTER_DETAILS, CHECK_PACKAGE_FAILURE); close hides the window and returns to the instructors.5800000identical
form2-instructor-insertInsert an instructor: PRE-INSERT takes INSTRUCTOR_ID from INSTRUCTOR_ID_SEQ and sets the created and modified columns (SYSDATE, :GLOBAL.USER_ID = 'QAFE').3820000identical
form2-student-insertInsert a student in FORM2. STUDENT_ID is the block's primary key, Required, on no canvas and set by no trigger: the save stops at FRM-40600 (Record has already been inserted) from Forms' primary-key check, so the insert never reaches the database and TRIGGER_STUDENT_BEFORE_INSERT does not run.4610000identical
form2-student-no-first-nameEmpty a student's first name and save. The form has no check; the database trigger TRIGGER_STUDENT_BEFORE_UPDATE raises ORA-20101 and Forms reports that it could not update the record.4600000identical
form2-student-updateChange a student's employer in the Students window and save; the database trigger on STUDENT checks the first name on the update (ORA-20101 when it is empty; not here).3710000identical
form2-studentsShow Students queries STUDENT into the Students window; Close hides it and returns to the instructors.2200000identical
form2-zip-lovDouble-click the instructor's ZIP: WHEN-MOUSE-DOUBLECLICK runs DO_KEY('LIST_VALUES') and the Zip Codes LOV opens; Escape closes it without a choice. The item's LOV is then used through Edit > Display List to choose another zip, and the change is saved (PRE-UPDATE).4631000identical
form3-browseQAFE_FORM3 queries COURSE; POST-QUERY counts each course's sections and enrolled students. Hide Student Count hides the count and a second press shows it again.2920100identical
form3-cost-invalidA course cost below 5 fails WHEN-VALIDATE-ITEM on COST: "Course cost has to be greater than 5". A valid cost then passes.3650000identical
form3-section-courseA section's course number is checked by WHEN-VALIDATE-ITEM: an unknown number gives "This course does not exist in the database."; an existing one passes, and the trigger's FETCH INTO :COURSE.COURSE_NO writes it into the master block's course number.4360000identical
form3-sectionsShow Sections opens the second window; SECTION's WHEN-NEW-BLOCK-INSTANCE queries the course's sections. Close Window runs GO_BACK_TO_HOME_SCREEN.1680000identical
total47354500

The same app with an Angular front end: 15 of 15

The check does not depend on what the new interface is built with. This app has a second front end written in Angular on the same back end and API, and the same scenarios, mapping, tolerance policy and waivers check it: 15 of 15 scenarios at parity, 0 defects, three runs from a fresh database byte-identical, and every diff byte-identical to the default front end's.

ScenariomatchtoleratedwaiveddefectunmappedThree runsDiff as the default front end
form1-browse590300identicalidentical
form1-insert682100identicalidentical
form1-update501000identicalidentical
form2-course-delete-refused3600000identicalidentical
form2-courses5800000identicalidentical
form2-instructor-insert3820000identicalidentical
form2-student-insert4610000identicalidentical
form2-student-no-first-name4600000identicalidentical
form2-student-update3710000identicalidentical
form2-students2200000identicalidentical
form2-zip-lov4631000identicalidentical
form3-browse2920100identicalidentical
form3-cost-invalid3650000identicalidentical
form3-section-course4360000identicalidentical
form3-sections1680000identicalidentical

The app is also rebuilt from native APEX components on its original Oracle schema, checked with the same scenarios: the APEX version.

Faithful to the original

The migrated app copies the original's behaviour, including what looks like a bug. These are the places a visitor will notice.

The interface is new; the behaviour is the original's, and the scenarios check it value by value. Some of that behaviour looks like a bug on a web page. It is kept on purpose, because the point of the demo is that nothing changed:

The original's Java client also announces the course number for screen readers when the cursor reaches it ("Course No.454"). That text is not on the screen; the browser leaves announcements to the screen reader (waiver S1).

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.

ModuleTriggersProgram unitsCoveredUnreachableBlocked
QAFE_FORM130300
QAFE_FORM21131310
QAFE_FORM381900
total2242510

Decision branches: 14, of which 10 covered, 4 unreachable (retirement candidates) and 0 blocked. Database PL/SQL: 10 unit(s) in the coverage report, 2 of 3 blocks covered. Exit check: PASS.

Every branch with its class
Trigger or unitBranchClassScenarios or reason
QAFE_FORM2/COURSE/ON-POPULATE-DETAILSqueried course - sections queriedcoveredform2-course-delete-refused, form2-courses
QAFE_FORM2/COURSE/ON-POPULATE-DETAILSnew course record - nothingunreachableFORM2's COURSE block has Insert Allowed No, so it never holds a NEW or INSERT record.
QAFE_FORM2/unit:QUERY_MASTER_DETAILSimmediate relation - detail queriedcoveredform2-course-delete-refused, form2-courses
QAFE_FORM2/unit:QUERY_MASTER_DETAILSdeferred relation - detail marked non-coordinatedunreachableCOURSE_SECTION, FORM2's only relation, has Deferred No.
QAFE_FORM2/unit:CLEAR_ALL_MASTER_DETAILSunchanged details - CLEAR_BLOCK(NO_VALIDATE)coveredform2-courses
QAFE_FORM2/unit:CLEAR_ALL_MASTER_DETAILSchanged detail - CLEAR_BLOCK(ASK_COMMIT)unreachableThe detail block SECTION has Insert, Update and Delete Allowed No (typing gives FRM-40200), so it is never CHANGED.
QAFE_FORM2/unit:CHECK_PACKAGE_FAILUREprevious built-in succeededcoveredform2-course-delete-refused, form2-courses
QAFE_FORM2/unit:CHECK_PACKAGE_FAILUREprevious built-in failed - raiseunreachableIt follows GO_BLOCK and GO_ITEM to SECTION or back to the starting item; FORM2 has no navigation or validation trigger that could make them fail, and SECTION is never changed.
QAFE_FORM3/COURSE.COST/WHEN-VALIDATE-ITEMcost below 5 - refusedcoveredform3-cost-invalid
QAFE_FORM3/COURSE.COST/WHEN-VALIDATE-ITEMcost 5 or more - acceptedcoveredform3-cost-invalid
QAFE_FORM3/SECTION.COURSE_NO/WHEN-VALIDATE-ITEMunknown course - refusedcoveredform3-section-course
QAFE_FORM3/SECTION.COURSE_NO/WHEN-VALIDATE-ITEMexisting course - acceptedcoveredform3-section-course
QAFE_FORM3/COURSE.HIDESTUDENTCOUNT/WHEN-BUTTON-PRESSEDvisible - hiddencoveredform3-browse
QAFE_FORM3/COURSE.HIDESTUDENTCOUNT/WHEN-BUTTON-PRESSEDhidden - showncoveredform3-browse
Database PL/SQL units
UnitTypeBlocksCoveredClass
ADF_EX10_01_TRIGGERSPACKAGE BODYNone0unreachable
STUDENTPACKAGE BODYNone0unreachable
STUDENTPKGPACKAGE BODYNone0unreachable
TRIGGER_STUDENT_BEFORE_INSERTTRIGGERNone0unreachable
TRIGGER_STUDENT_BEFORE_UPDATETRIGGER32partly covered
V_EX01_01_STUDENT_TRGTRIGGERNone0unreachable
V_EX10_01_STUDENT_TRGTRIGGERNone0unreachable
V_QAFE_FORM1_COURSE_TRGTRIGGERNone0unreachable
V_QAFE_FORM2_INSTRUCTOR_TRGTRIGGERNone0unreachable
V_QAFE_FORM2_STUDENT_TRGTRIGGERNone0unreachable

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.

Waived: 1 waivers are proposed for the customer's decision; 1 of them cover 5 values here, where the migrated app deliberately does not copy the original (see waivers).

IdWaiverValuesReason
S1Screen-reader announcement of the COURSE_NO item read as a message5After the cursor enters COURSE.COURSE_NO by record navigation (Next Record, the first record of a query, the Hide Student Count button), the Forms 14.1.2 Java client announces the item for assistive technology: its prompt or hint ("Course No." in QAFE_FORM1, "Course No Required" in QAFE_FORM3) followed by the current value, e.g. "Course No.454". The recorder reads this accessible text where the message line lives, but nothing is written to the visible message line (the screenshots of those steps show an empty status bar) and no form code issues it. A browser target leaves screen-reader announcements to the browser (the item's label is its accessible name), so the visible message line stays empty, as on screen in the source. Only these announcement values are waived; every other message is still compared.