2
* This file is part of Checkbox
4
* Copyright 2014, 2015 Canonical Ltd.
7
* - Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
8
* - Maciej Kisielewski <maciej.kisielewski@canonical.com>
10
* This program is free software; you can redistribute it and/or modify
11
* it under the terms of the GNU General Public License as published by
12
* the Free Software Foundation; version 3.
14
* This program is distributed in the hope that it will be useful,
15
* but WITHOUT ANY WARRANTY; without even the implied warranty of
16
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
* GNU General Public License for more details.
19
* You should have received a copy of the GNU General Public License
20
* along with this program. If not, see <http://www.gnu.org/licenses/>.
23
import Ubuntu.Components 1.1
24
import Ubuntu.Components.Popups 0.1
25
import QtQuick.Layouts 1.1
26
import io.thp.pyotherside 1.4
28
import "components/ErrorLogic.js" as ErrorLogic
29
import "components/CbtDialogLogic.js" as CbtDialogLogic
33
\brief MainView with a Label and Button elements.
39
// objectName for functional testing purposes (autopilot-qt5)
40
objectName: "mainView"
42
// Note! applicationName needs to match the "name" field of the click manifest
43
applicationName: "com.canonical.certification.checkbox-touch"
46
This property enables the application to change orientation
47
when the device is rotated. The default is false.
49
//automaticOrientation: true
54
useDeprecatedToolbar: false
56
// appSettings serves as application-wide storage for global variables
57
// it has to have at least one entry to be constructed
58
property var appSettings: {
59
"applicationName" : applicationName,
60
"revision": "unknown revision",
62
"providersDir": "providers",
70
help: i18n.tr("Run Checkbox-Touch in autopilot-testing mode")
75
help: i18n.tr("Write only warnings and errors to standard error")
80
valueNames: "PATH_TO_SETTINGS"
81
help: i18n.tr("Path to a file containing checkbox-touch settings")
86
Component.onCompleted: {
87
i18n.domain = "com.ubuntu.checkbox";
88
if (args.values["autopilot"]) {
89
// autopilot-testing mode
90
appSettings["testplan"] = "2015.com.canonical.certification::checkbox-touch-autopilot";
91
appSettings["providersDir"] = "tests/autopilot/autopilot-provider";
92
appSettings["log-level"] = "warning";
94
// normal execution - load settings.json file
95
var xhr = new XMLHttpRequest;
96
xhr.open("GET", args.values["settings"]);
97
xhr.onreadystatechange = function() {
98
if (xhr.readyState == XMLHttpRequest.DONE) {
100
var newAppSettings = JSON.parse(xhr.responseText);
102
// if we cannot parse settings.json, we should leave
103
// deafult values of appSettings
104
console.error("Could not parse settings.json. Using default values");
106
// overwrite/add appSettings' attributes that got loaded
107
for (var attr in newAppSettings) {
108
appSettings[attr] = newAppSettings[attr];
114
if (args.values["quiet"]) {
115
// monkey-patch console.log and console.info to do nothing
116
console.log = function() {};
117
console.info = function() {};
118
appSettings["log-level"] = "warning";
124
// Pyotherside python object that we use to talk to all of plainbox
129
console.log("Pyotherside version " + pluginVersion());
130
console.log("Python version " + pythonVersion());
131
// A bit hacky but that's where the python code is
132
addImportPath(Qt.resolvedUrl('py/'));
133
// Import path for plainbox and potentially other python libraries
134
addImportPath(Qt.resolvedUrl('lib/py'))
135
setHandler('command_output', commandOutputPage.addText);
139
// gets triggered when python object is ready to be used
143
console.error("python error: " + traceback);
144
ErrorLogic.showError(mainView, "python error: " + traceback, Qt.quit);
146
onReceived: console.log("pyotherside.send: " + data)
149
// Component representing our application
150
CheckboxTouchApplication {
153
property var incompleteSessions: []
155
console.log("Plainbox version " + plainboxVersion);
156
console.log("Checkbox Touch version " + applicationVersion);
157
aboutPage.versionInfo = {
158
"checkbox_touch" : applicationVersion,
159
"plainbox" : plainboxVersion
161
getIncompleteSessions(function(sessions) {
162
incompleteSessions = sessions;
163
resumeSessionPage.incompleteSessionCount = sessions.length;
165
resumeOrStartSession();
168
welcomePage.enableButton()
170
Component.onCompleted: {
171
// register to py.initiated signal
172
py.onInitiated.connect(function() {
173
construct("checkbox_touch.create_app_object", []);
181
Component.onCompleted: {
182
// register to py.initiated signal
183
py.onInitiated.connect(function() {
184
py.importModule("checkbox_touch", function() {
185
construct("checkbox_touch.get_qml_logger",
186
[appSettings["log-level"] || "info"]);
194
Component.onCompleted: push(welcomePage)
199
// TRANSLATORS: %1 means program version, %2 repository revision and %3
200
// date when the package was built
201
// TRANSLATORS: keep the '\n' characters at the end of each line
202
welcomeText: i18n.tr("Welcome to Checkbox Touch\nVersion: %1\n(%2 %3)")
203
.arg(app.applicationVersion).arg(appSettings.revision).arg(appSettings.clickBuildDate)
204
onStartTestingTriggered: {
205
if (appSettings.testplan != "") {
206
app.rememberTestplan(appSettings.testplan, function() {
207
categorySelectionPage.setup();
211
app.getTestplans(function(response) {
212
var tp_list = response.testplan_info_list;
213
if (tp_list.length < 2 && tp_list.length > 0) {
214
app.rememberTestplan(tp_list[0].mod_id, function() {
215
categorySelectionPage.setup();
219
testplanSelectionPage.setup(tp_list)
225
onAboutClicked: pageStack.push(aboutPage)
236
property alias progressText: _progressText.text
237
property alias value: _progressBar.value
238
property alias maximumValue: _progressBar.maximumValue
242
verticalCenter: parent.verticalCenter
243
bottomMargin: units.gu(1.5)
244
rightMargin: units.gu(1)
245
// leftMargin should compensate for potential 'back' action that might appear on next page
246
// so the whole progressHeader stays in the same spot on the screen throughout the session
247
leftMargin: pageStack.depth == 1 ? units.gu(5) : units.gu(1)
251
text: pageStack.currentPage ? pageStack.currentPage.title : ""
253
font.weight: Font.Light
254
anchors.verticalCenter: parent.verticalCenter
259
font.weight: Font.Light
260
anchors.right: parent.right
261
anchors.bottom: parent.bottom
267
implicitWidth: parent.width
271
function update(test) {
272
progressHeader.visible = true;
273
progressHeader.progressText = Number(test.test_number / test.tests_count * 100.0).toFixed(0) + "% ("+test.test_number + "/" + test.tests_count + ")";
274
progressHeader.value = test.test_number
275
progressHeader.maximumValue = test.tests_count
280
id: resumeSessionPage
281
onRerunLast: app.resumeSession(true, processNextTest)
282
onContinueSession: app.resumeSession(false, processNextTest)
283
resumeText: i18n.tr("Checkbox session got suspended.\nDo you want \
284
to rerun last test, continue to the next test, or start a new session?")
287
pageStack.push(welcomePage);
290
onDeleteIncomplete: {
291
app.deleteOldSessions(app.incompleteSessions, function() {
293
pageStack.push(welcomePage);
300
id: testplanSelectionPage
301
title: i18n.tr("Select test plan")
303
largeBuffer: args.values["autopilot"]
305
function setup(testplan_info_list) {
306
if (testplan_info_list.length<1) {
307
ErrorLogic.showError(mainView, "Test plan missing", Qt.quit);
311
for (var i=0; i<testplan_info_list.length; i++) {
312
var testplan_info = testplan_info_list[i];
313
model.append(testplan_info);
316
pageStack.push(testplanSelectionPage);
319
app.rememberTestplan(selected_id_list[0], function(response) {
320
categorySelectionPage.setup(unlatchContinue);
326
id: categorySelectionPage
327
objectName: "categorySelectionPage"
328
title: i18n.tr("Select categories")
329
largeBuffer: args.values["autopilot"]
331
function setup(continuation) {
332
app.getCategories(function(response) {
333
var uncategorised_id = "2013.com.canonical.plainbox::uncategorised"
334
if (response.category_info_list.length === 1 &&
335
response.category_info_list[0].mod_id == uncategorised_id) {
336
selectionDone(uncategorised_id);
338
var category_info_list = response.category_info_list;
340
for (var i=0; i<category_info_list.length; i++) {
341
var category_info = category_info_list[i];
342
model.append(category_info);
345
pageStack.push(categorySelectionPage);
347
// if called from welcome page, no continuation is given
348
if (continuation) continuation();
353
app.rememberCategorySelection(selected_id_list, function(response) {
354
testSelectionPage.setup(unlatchContinue);
361
id: testSelectionPage
362
objectName: "testSelectionPage"
363
title: i18n.tr("Select tests")
364
continueText: i18n.tr("Start testing")
365
largeBuffer: args.values["autopilot"]
367
function setup(continuation) {
368
app.getTests(function(response) {
370
var test_info_list = response.test_info_list;
371
for (var i=0; i<test_info_list.length; i++) {
372
model.append(test_info_list[i]);
375
pageStack.push(testSelectionPage);
381
app.rememberTestSelection(selected_id_list, function() {
389
id: rerunSelectionPage
390
objectName: "rerunSelectionPage"
391
title: i18n.tr("Select tests to re-run")
392
continueText: state == "empty selection" ?
393
i18n.tr("Finish") : i18n.tr("Re-run")
395
largeBuffer: args.values["autopilot"]
397
function setup(rerunCandidates, continuation) {
399
for (var i=0; i<rerunCandidates.length; i++) {
400
model.append(rerunCandidates[i]);
403
pageStack.push(rerunSelectionPage)
406
if (!selected_id_list.length) {
411
app.rememberTestSelection(selected_id_list, function() {
426
id: commandOutputPage
428
__customHeaderContents: progressHeader;
431
* Create a page from a Component defined in `url` with a common
432
* progress header if `test` is supplied.
433
* If Component definition has errors, display a proper popup.
435
function createPage(url, test) {
436
var pageComponent = Qt.createComponent(Qt.resolvedUrl(url));
437
if (pageComponent.status == Component.Error) {
438
var msg = i18n.tr("Could not create component '") + url + "'\n" + pageComponent.errorString();
440
ErrorLogic.showError(mainView, msg, Qt.quit, i18n.tr("Quit"));
442
var pageObject = pageComponent.createObject();
444
pageObject.test = test;
445
pageObject.__customHeaderContents = progressHeader;
446
progressHeader.update(test)
452
function resumeOrStartSession() {
453
app.isSessionResumable(function(result) {
454
if(result.resumable === true) {
456
pageStack.push(resumeSessionPage);
458
if (result.errors_encountered) {
459
ErrorLogic.showError(mainView, i18n.tr("Could not resume session."),
461
i18n.tr("Start new session"));
469
function processNextTest() {
470
app.getNextTest(function(test) {
472
if (test.plugin === undefined) {
473
return showResultsScreen();
476
// running this test will require to be run as a different
477
// user (therefore requiring user to enter sudo password)
478
if (!appSettings.sudoPasswordProvided) {
479
// ask user for password
480
var rememberContinuation = function(pass) {
481
passwordDialog.passwordEntered.disconnect(rememberContinuation);
482
app.rememberPassword(pass, function(){
483
appSettings.sudoPasswordProvided = true;
487
var cancelContinuation = function() {
488
passwordDialog.dialogCancelled.disconnect(cancelContinuation);
489
test.outcome = "skip";
492
passwordDialog.passwordEntered.connect(rememberContinuation);
493
passwordDialog.dialogCancelled.connect(cancelContinuation);
494
PopupUtils.open(passwordDialog.dialogComponent);
502
function performTest(test) {
503
switch (test['plugin']) {
505
performManualTest(test);
507
case 'user-interact-verify':
508
performUserInteractVerifyTest(test);
514
performAutomatedTest(test);
517
performUserVerifyTest(test);
519
case 'user-interact':
520
performUserInteractTest(test);
523
if (test.flags.indexOf("confined") > -1)
524
performConfinedQmlTest(test);
526
performQmlTest(test);
529
test.outcome = "skip";
534
function completeTest(test) {
535
app.registerTestResult(test, processNextTest);
538
function runTestActivity(test, continuation) {
539
commandOutputPage.clear();
540
app.runTestActivity(test, continuation);
544
function showResultsScreen() {
545
var endTesting = function() {
547
app.clearSession(function() {
549
pageStack.push(welcomePage);
552
var saveReport = function() {
553
app.exportResults('2013.com.canonical.plainbox::html', [], function(uri) {
554
var htmlReportUrl = uri;
555
app.exportResults('2013.com.canonical.plainbox::xlsx', ["with-sys-info", "with-summary", "with-job-description", "with-text-attachments", "with-unit-categories"], function(uri) {
556
CbtDialogLogic.showDialog(mainView, i18n.tr("Reports have been saved to your Documents folder"),
557
[{ "text": i18n.tr("OK"), "color": UbuntuColors.green}, {"text": i18n.tr("View Report"), "color": UbuntuColors.green, "onClicked": function(uri) {
558
var webviewer = Qt.createComponent(Qt.resolvedUrl("components/WebViewer.qml")).createObject();
559
webviewer.uri = htmlReportUrl;
560
pageStack.push(webviewer);
565
var submitReport = function(resultsPage) {
566
// resultsPage param is for having control over unlatching
567
getSubmissionInput(function() {
568
app.submitResults(appSettings["submission"], function(reply) {
569
// pretty-stringify reply
571
for (var i in reply) {
572
s += i + ': ' + reply[i] + '\n';
574
CbtDialogLogic.showDialog(
576
i18n.tr("Report has been submited.\n" + s),
577
[{"text": i18n.tr("OK"), "color": UbuntuColors.green}]);
580
ErrorLogic.showError(mainView,
581
i18n.tr("Could not submit results. Reason:\n" + error),
584
resultsPage.unlatchSubmission();
588
resultsPage.unlatchSubmission();
593
app.getRerunCandidates(function(rerunCandidates) {
594
app.getResults(function(results) {
595
var resultsPage = createPage("components/ResultsPage.qml");
596
resultsPage.results = results;
597
if (appSettings["submission"]) {
598
resultsPage.submissionName = appSettings["submission"].name;
600
resultsPage.endTesting.connect(endTesting);
601
resultsPage.saveReportClicked.connect(saveReport);
602
resultsPage.submitReportClicked.connect(function() {submitReport(resultsPage);});
603
if (rerunCandidates.length>0) {
604
resultsPage.rerunEnabled = true;
605
resultsPage.rerunTests.connect(function() {
606
rerunSelectionPage.setup(rerunCandidates);
609
pageStack.push(resultsPage);
614
function performAutomatedTest(test) {
615
var automatedTestPage = createPage("components/AutomatedTestPage.qml", test);
616
pageStack.push(automatedTestPage);
617
runTestActivity(test, completeTest);
620
function performManualTest(test) {
621
runTestActivity(test, function(test) {
622
var manualIntroPage = createPage("components/ManualIntroPage.qml", test);
623
manualIntroPage.testDone.connect(completeTest);
624
manualIntroPage.continueClicked.connect(function() { showVerificationScreen(test); });
625
pageStack.push(manualIntroPage);
629
function performUserInteractVerifyTest(test) {
630
var InteractIntroPage = createPage("components/InteractIntroPage.qml", test);
631
InteractIntroPage.testStarted.connect(function() {
632
runTestActivity(test, function(test) {
633
InteractIntroPage.stopActivity();
634
showVerificationScreen(test);
637
InteractIntroPage.testDone.connect(completeTest);
638
pageStack.push(InteractIntroPage);
641
function performUserInteractTest(test) {
642
var InteractIntroPage = createPage("components/InteractIntroPage.qml", test);
643
InteractIntroPage.testStarted.connect(function() {
644
runTestActivity(test, function(test) {
645
InteractIntroPage.stopActivity();
646
var userInteractSummaryPage = createPage("components/UserInteractSummaryPage.qml", test);
647
userInteractSummaryPage.testDone.connect(completeTest);
648
pageStack.push(userInteractSummaryPage);
651
InteractIntroPage.testDone.connect(completeTest);
652
pageStack.push(InteractIntroPage);
655
function performUserVerifyTest(test) {
656
var InteractIntroPage = createPage("components/InteractIntroPage.qml", test);
657
InteractIntroPage.testDone.connect(completeTest);
658
InteractIntroPage.testStarted.connect(function() {
659
runTestActivity(test, function(test) { showVerificationScreen(test); } );
661
pageStack.push(InteractIntroPage);
664
function performQmlTest(test) {
665
runTestActivity(test, function(test) {
666
var qmlNativePage = createPage("components/QmlNativePage.qml", test);
667
qmlNativePage.testDone.connect(completeTest);
668
pageStack.push(qmlNativePage);
671
function performConfinedQmlTest(test) {
672
runTestActivity(test, function(test) {
673
var qmlNativePage = createPage("components/QmlConfinedPage.qml", test);
674
qmlNativePage.applicationVersion = app.applicationVersion;
675
qmlNativePage.testDone.connect(completeTest);
676
pageStack.push(qmlNativePage);
680
function showVerificationScreen(test) {
681
var verificationPage = createPage("components/TestVerificationPage.qml", test);
682
var maybeCommentVerification = function(test) {
683
if (test.outcome == 'fail' &&
684
test.flags.indexOf('explicit-fail') > -1) {
685
commentsDialog.commentDefaultText = test["comments"] || "";
686
commentsDialog.commentAdded.connect(function(comment) {
687
test["comments"] = comment;
690
PopupUtils.open(commentsDialog.dialogComponent);
695
verificationPage.testDone.connect(maybeCommentVerification);
696
pageStack.push(verificationPage);
698
function getSubmissionInput(continuation, cancelContinuation) {
699
if (appSettings.submission.inputForm) {
700
var dlg_cmp = Qt.createComponent(Qt.resolvedUrl(appSettings.submission.inputForm));
701
var dlg = dlg_cmp.createObject(mainView);
703
dlg.cancelClicked.connect(function() {
704
cancelContinuation();
708
dlg.submissionDetailsFilled.connect(function(submissionDetails) {
709
for (var attr in submissionDetails) {
710
appSettings.submission[attr] = submissionDetails[attr];
714
PopupUtils.open(dlg.dialogComponent);
715
return; // inputForm gets precedence over input
718
if (!appSettings.submission.input) {
719
// no input to process
723
var input_vars = appSettings.submission.input.slice(); //copy array
725
// Because of the asynchronous nature of qml we cannot just launch
726
// N number of popups each asking for one submission variable
727
var process_input = function() {
728
if (input_vars.length > 0) {
729
var input = input_vars.shift();
730
var dlg = Qt.createComponent(Qt.resolvedUrl("components/InputDialog.qml")).createObject(mainView);
731
dlg.prompt = input.prompt;
732
dlg.textEntered.connect(function(text) {
733
appSettings.submission[input.paramName] = text;
736
dlg.cancelClicked.connect(function() {
737
cancelContinuation();
740
PopupUtils.open(dlg.dialogComponent);
747
function gcAndStartSession() {
748
// delete sessions that won't be resumed (NOT incomplete sessions)
749
// and start a new session
750
app.deleteOldSessions([], function() {