Merge branch '4.4' into master
[civicrm-core.git] / tests / phpunit / WebTest / Campaign / SurveyUsageScenarioTest.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.5 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2014 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
13 | |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
18 | |
19 | You should have received a copy of the GNU Affero General Public |
20 | License along with this program; if not, contact CiviCRM LLC |
21 | at info[AT]civicrm[DOT]org. If you have questions about the |
22 | GNU Affero General Public License or the licensing of CiviCRM, |
23 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
24 +--------------------------------------------------------------------+
25 */
26
27 require_once 'CiviTest/CiviSeleniumTestCase.php';
28 class WebTest_Campaign_SurveyUsageScenarioTest extends CiviSeleniumTestCase {
29
30 protected function setUp() {
31 parent::setUp();
32 }
33
34 function testSurveyUsageScenario() {
35 $this->webtestLogin('admin');
36
37 // Create new group
38 $title = substr(sha1(rand()), 0, 7);
39 $groupName = $this->WebtestAddGroup();
40
41 // Adding contact
42 $firstName1 = substr(sha1(rand()), 0, 7);
43 $this->webtestAddContact($firstName1, "Smith", "$firstName1.smith@example.org");
44
45 // add contact to group
46 // visit group tab
47 $this->click("css=li#tab_group a");
48 $this->waitForElementPresent("css=#group_id option");
49
50 // add to group
51 $this->select("group_id", "label=$groupName");
52 $this->click("_qf_GroupContact_next");
53 $this->waitForElementPresent('link=Remove');
54
55 $firstName2 = substr(sha1(rand()), 0, 7);
56 $this->webtestAddContact($firstName2, "John", "$firstName2.john@example.org");
57
58 // add contact to group
59 // visit group tab
60 $this->click("css=li#tab_group a");
61 $this->waitForElementPresent("css=#group_id option");
62
63 // add to group
64 $this->select("group_id", "label=$groupName");
65 $this->click("_qf_GroupContact_next");
66 $this->waitForElementPresent('link=Remove');
67
68 // Enable CiviCampaign module if necessary
69 $this->enableComponents(array('CiviCampaign'));
70
71 // add the required permission
72 $this->changePermissions(array('edit-2-administer-civicampaign'));
73
74 // Log in as normal user
75 $this->webtestLogin();
76
77 $this->openCiviPage("campaign/add", "reset=1", "_qf_Campaign_upload-bottom");
78
79 $this->type("title", "Campaign $title");
80
81 // select the campaign type
82 $this->select("campaign_type_id", "value=2");
83
84 // fill in the description
85 $this->type("description", "This is a test campaign");
86
87 // include groups for the campaign
88 $this->addSelection("includeGroups-f", "label=$groupName");
89 $this->click("//option[@value=4]");
90 $this->click("add");
91
92 // fill the end date for campaign
93 $this->webtestFillDate("end_date", "+1 year");
94
95 // select campaign status
96 $this->select("status_id", "value=2");
97
98 // click save
99 $this->click("_qf_Campaign_upload-bottom");
100 $this->waitForPageToLoad($this->getTimeoutMsec());
101
102 $this->waitForText('crm-notification-container', "$title");
103
104 // create a custom data set for activities -> survey
105 $this->openCiviPage('admin/custom/group', "action=add&reset=1", "_qf_Group_next-bottom");
106 // fill in a unique title for the custom group
107 $this->type("title", "Group $title");
108
109 // select the group this custom data set extends
110 $this->select("extends[0]", "value=Activity");
111 $this->waitForElementPresent("//select[@id='extends_1']");
112 $this->select("//select[@id='extends_1']", "label=Survey");
113
114 // save the custom group
115 $this->click("_qf_Group_next-bottom");
116 $this->waitForPageToLoad($this->getTimeoutMsec());
117
118 // add a custom field to the custom group
119 $this->type("label", "Field $title");
120
121 $this->select("data_type[1]", "value=Radio");
122
123 $this->waitForElementPresent("option_label_1");
124
125 // create a set of options
126 $this->type("option_label_1", "Option $title 1");
127 $this->type("option_value_1", "1");
128
129 $this->type("option_label_2", "Option $title 2");
130 $this->type("option_value_2", "2");
131
132 // save the custom field
133 $this->click("_qf_Field_next-bottom");
134
135 $this->waitForElementPresent("newCustomField");
136 $this->waitForText('crm-notification-container', "$title");
137
138 // create a profile for campaign
139 $this->openCiviPage("admin/uf/group/add", "action=add&reset=1", "_qf_Group_next-bottom");
140
141 // fill in a unique title for the profile
142 $this->type("title", "Profile $title");
143
144 // save the profile
145 $this->click("_qf_Group_next-bottom");
146
147 $this->waitForElementPresent("_qf_Field_next-bottom");
148 $this->waitForText('crm-notification-container', "$title");
149
150 // add a profile field for activity
151 $this->select("field_name[0]", "value=Activity");
152 $this->waitForElementPresent("field_name[1]");
153 $this->select("field_name[1]", "label=Field $title :: Group $title");
154
155 $this->click("_qf_Field_next-bottom");
156 $this->waitForPageToLoad($this->getTimeoutMsec());
157 $this->waitForText('crm-notification-container', "$title");
158
159 // create a survey
160 $this->openCiviPage("survey/add", "reset=1", "_qf_Main_upload-bottom");
161
162 // fill in a unique title for the survey
163 $this->type("title", "Survey $title");
164
165 // select the created campaign
166 $this->select("campaign_id", "label=Campaign $title");
167
168 // select the activity type
169 $this->select("activity_type_id", "label=Survey");
170
171 // fill in reserve survey respondents
172 $this->type("default_number_of_contacts", 50);
173
174 // fill in interview survey respondents
175 $this->type("max_number_of_contacts", 100);
176
177 // release frequency
178 $this->type("release_frequency", 2);
179
180 $this->click("_qf_Main_upload-bottom");
181 $this->waitForElementPresent("_qf_Questions_upload_next-bottom");
182
183 // Select the profile for the survey
184 $this->select("//form[@id='Questions']/div[2]/table/tbody/tr[1]/td[2]/div/div/span/select", "label=New Individual");
185
186 // select the question created for the survey
187
188 $this->select("//form[@id='Questions']/div[2]/table/tbody/tr[2]/td[2]/div/div/span/select", "label=Profile $title");
189 $this->click("_qf_Questions_upload_next-bottom");
190
191 // create a set of options for Survey Responses _qf_Results_upload_done-bottom
192 $this->waitForElementPresent('_qf_Results_upload_done-bottom');
193 $this->type("//input[@id='option_label_1']", "Label $title 1");
194 $this->type("//input[@id='option_value_1']", "1");
195
196 $this->type("//input[@id='option_label_2']", "Label $title 2");
197 $this->type("//input[@id='option_value_2']", "2");
198 $this->click('_qf_Results_upload_done-bottom');
199 $this->waitForElementPresent("//div[@id='search_form_survey']");
200 $this->waitForText('crm-notification-container', "Results");
201
202 // Reserve Respondents
203 $this->openCiviPage("survey/search", "reset=1&op=reserve", "_qf_Search_refresh");
204
205 // search for the respondents
206 $this->select("campaign_survey_id", "label=Survey $title");
207
208 $this->click("_qf_Search_refresh");
209
210 $this->waitForElementPresent("Go");
211 $this->click("CIVICRM_QFID_ts_all_4");
212 $this->select('task', "Reserve Respondents");
213 $this->click("Go");
214
215 $this->waitForElementPresent("_qf_Reserve_done_reserve-bottom");
216 $this->click("_qf_Reserve_done_reserve-bottom");
217 $this->waitForPageToLoad($this->getTimeoutMsec());
218 $this->waitForText('crm-notification-container', "2 Contact\(s\) have been reserved");
219
220 // Interview Respondents
221 $this->openCiviPage("survey/search", "reset=1&op=interview", "_qf_Search_refresh");
222
223 // search for the respondents
224 $this->select("campaign_survey_id", "label=Survey $title");
225
226 $this->click("_qf_Search_refresh");
227
228 $this->waitForElementPresent("Go");
229 $this->click("CIVICRM_QFID_ts_all_4");
230 $this->click("Go");
231
232 $this->waitForElementPresent("_qf_Interview_cancel_interview");
233
234 $this->click("CIVICRM_QFID_1_2");
235 $this->select("css=#voterRecords .odd .result select", "value=Label $title 1");
236 $this->click("css=#voterRecords .odd td a");
237
238 $this->click("CIVICRM_QFID_2_8");
239 $this->select("css=#voterRecords .even .result select", "value=Label $title 2");
240 $this->click("css=#voterRecords .even td a");
241
242 $this->click("_qf_Interview_cancel_interview");
243 $this->waitForPageToLoad($this->getTimeoutMsec());
244
245 // add a contact to the group to test release respondents
246 $firstName3 = substr(sha1(rand()), 0, 7);
247 $this->webtestAddContact($firstName3, "James", "$firstName3.james@example.org");
248 $id = $this->urlArg('cid');
249 $sortName3 = "James, $firstName3";
250
251 // add contact to group
252 // visit group tab
253 $this->click("css=li#tab_group a");
254 $this->waitForElementPresent("css=#group_id option");
255
256 // add to group
257 $this->select("group_id", "label=$groupName");
258 $this->click("_qf_GroupContact_next");
259 $this->waitForElementPresent('link=Remove');
260
261 // Reserve Respondents
262 $this->openCiviPage("survey/search", "reset=1&op=reserve", "_qf_Search_refresh");
263
264 // search for the respondents
265 $this->select("campaign_survey_id", "label=Survey $title");
266
267 $this->click("_qf_Search_refresh");
268
269 $this->waitForElementPresent("Go");
270 $this->click("CIVICRM_QFID_ts_all_4");
271 $this->select('task', "Reserve Respondents");
272 $this->click("Go");
273
274 $this->waitForElementPresent("_qf_Reserve_done_reserve-bottom");
275 $this->click("_qf_Reserve_done_reserve-bottom");
276 $this->waitForPageToLoad($this->getTimeoutMsec());
277 $this->waitForText('crm-notification-container', "1 Contact\(s\) have been reserved");
278
279 // Release Respondents
280 $this->openCiviPage("survey/search", "reset=1&op=release", "_qf_Search_refresh");
281
282 // search for the respondents
283 $this->select("campaign_survey_id", "label=Survey $title");
284
285 $this->click("_qf_Search_refresh");
286
287 $this->waitForElementPresent("Go");
288 $this->click("xpath=id('mark_x_$id')");
289 $this->click("CIVICRM_QFID_ts_sel_2");
290 $this->select("task", "label=Release Respondents");
291 $this->clickLink("Go", "_qf_Release_done-bottom", FALSE);
292 $this->clickLink("_qf_Release_done-bottom", 'access', FALSE);
293 $this->waitForText('crm-notification-container', "released");
294
295 // check whether contact is available for reserving again
296 $this->openCiviPage("survey/search", "reset=1&op=reserve", "_qf_Search_refresh");
297
298 // search for the respondents
299 $this->select("campaign_survey_id", "label=Survey $title");
300
301 $this->click("_qf_Search_refresh");
302 $this->waitForPageToLoad($this->getTimeoutMsec());
303 $this->assertTrue($this->isTextPresent("1 Result"), "Result didn't show up after saving!");
304 }
305
306 function testSurveyReportTest() {
307 $this->webtestLogin('admin');
308
309 // Enable CiviCampaign module if necessary
310 $this->enableComponents(array('CiviCampaign'));
311
312 // add the required permission
313 $this->changePermissions('edit-2-administer-civicampaign');
314
315 $this->webtestLogin();
316
317 // Create new group
318 $title = substr(sha1(rand()), 0, 7);
319 $groupName = $this->WebtestAddGroup();
320
321 // Adding contact
322 $firstName1 = substr(sha1(rand()), 0, 7);
323 $this->webtestAddContact($firstName1, "Smith", "$firstName1.smith@example.org");
324 $id1 = $this->urlArg('cid');
325
326 // add contact to group
327 // visit group tab
328 $this->click("css=li#tab_group a");
329 $this->waitForElementPresent('css=#group_id option');
330
331 // add to group
332 $this->select("group_id", "label=$groupName");
333 $this->click("_qf_GroupContact_next");
334 $this->waitForElementPresent('link=Remove');
335
336 $firstName2 = substr(sha1(rand()), 0, 7);
337 $this->webtestAddContact($firstName2, "John", "$firstName2.john@example.org");
338 $id2 = $this->urlArg('cid');
339
340 // add contact to group
341 // visit group tab
342 $this->click("css=li#tab_group a");
343 $this->waitForElementPresent('css=#group_id option');
344
345 // add to group
346 $this->select("group_id", "label=$groupName");
347 $this->click("_qf_GroupContact_next");
348 $this->waitForElementPresent('link=Remove');
349
350 // Create custom group and add custom data fields
351 $this->openCiviPage("admin/custom/group", "reset=1");
352
353 $this->click("link=Add Set of Custom Fields");
354 $this->waitForElementPresent('_qf_Group_cancel-bottom');
355
356 $customGroup = "Custom Group $title";
357 $this->type('title', "$customGroup");
358 $this->select('extends[0]', "value=Contact");
359 $this->click('_qf_Group_next-bottom');
360 $this->waitForElementPresent('_qf_Field_cancel-bottom');
361 $this->waitForText('crm-notification-container', $customGroup);
362
363 // Add custom fields
364 $field1 = "Checkbox $title";
365 $this->type('label', $field1);
366 $this->select('data_type[1]', "value=CheckBox");
367 $this->waitForElementPresent('option_label_2');
368
369 // add multiple choice options
370 $label1 = "Check $title One";
371 $value1 = 1;
372 $this->type('option_label_1', $label1);
373 $this->type('option_value_1', $value1);
374
375 $label2 = "Check $title Two";
376 $value2 = 2;
377 $this->type('option_label_2', $label2);
378 $this->type('option_value_2', $value2);
379
380 $this->click("link=another choice");
381
382 $label3 = "Check $title Three";
383 $value3 = 3;
384 $this->type('option_label_3', $label3);
385 $this->type('option_value_3', $value3);
386
387 $this->click('_qf_Field_next-bottom');
388 $this->waitForPageToLoad($this->getTimeoutMsec());
389 $this->waitForText('crm-notification-container', $field1);
390
391 // Create a profile for survey
392 $this->openCiviPage("admin/uf/group", "reset=1");
393
394 $this->click("link=Add Profile");
395 $this->waitForElementPresent('_qf_Group_cancel-bottom');
396
397 $surveyProfile = "Survey Profile $title";
398 $this->type('title', $surveyProfile);
399 $this->click('_qf_Group_next-bottom');
400 $this->waitForElementPresent('_qf_Field_cancel-bottom');
401 $this->waitForText('crm-notification-container', $surveyProfile);
402
403 // Add fields to the profile
404 // Phone ( Primary )
405 $this->select('field_name[0]', "value=Contact");
406 $this->select('field_name[1]', "value=phone");
407 $this->click('field_name[1]');
408 $this->select('visibility', "value=Public Pages and Listings");
409 $this->check('is_searchable');
410 $this->check('in_selector');
411 $this->clickLink('_qf_Field_next_new-bottom', '_qf_Field_cancel-bottom');
412
413 // Custom Data Fields
414 $this->select('field_name[0]', "value=Contact");
415 $this->select('field_name[1]', "label=$field1 :: $customGroup");
416 $this->click('field_name[1]');
417 $this->select('visibility', "value=Public Pages and Listings");
418 $this->check('is_searchable');
419 $this->check('in_selector');
420 $this->click('_qf_Field_next-bottom');
421 $this->waitForPageToLoad($this->getTimeoutMsec());
422
423 // Create a survey
424 $this->openCiviPage("survey/add", "reset=1", "_qf_Main_upload-bottom");
425
426 // fill in a unique title for the survey
427 $surveyTitle = "Survey $title";
428 $this->type('title', $surveyTitle);
429
430 // select the created campaign
431 //$this->select("campaign_id", "label=Campaign $title");
432
433 // select the activity type
434 $this->select("activity_type_id", "label=Survey");
435
436 // fill in reserve survey respondents
437 $this->type("default_number_of_contacts", 50);
438
439 // fill in interview survey respondents
440 $this->type("max_number_of_contacts", 100);
441
442 // release frequency
443 $this->type("release_frequency", 2);
444
445 $this->click("_qf_Main_upload-bottom");
446 $this->waitForElementPresent("_qf_Questions_upload_next-bottom");
447
448 //Select the profile for the survey
449 $this->select("//form[@id='Questions']/div[2]/table/tbody/tr[1]/td[2]/div/div/span/select", "label=New Individual");
450
451 // select the question created for the survey
452
453 $this->select("//form[@id='Questions']/div[2]/table/tbody/tr[2]/td[2]/div/div/span/select", "label=$surveyProfile");
454 $this->click("_qf_Questions_upload_next-bottom");
455
456 // create a set of options for Survey Responses _qf_Results_upload_done-bottom
457 $this->waitForElementPresent('_qf_Results_upload_done-bottom');
458 $optionLabel1 = "Label $title 1";
459 $this->type("//input[@id='option_label_1']", "$optionLabel1");
460 $this->type("//input[@id='option_value_1']", "1");
461
462 $optionLabel2 = "Label $title 2";
463 $this->type("//input[@id='option_label_2']", "$optionLabel2");
464 $this->type("//input[@id='option_value_2']", "2");
465 $this->type("//input[@id='report_title']", "Survey $title");
466 $this->click('_qf_Results_upload_done-bottom');
467 $this->waitForElementPresent("//div[@id='search_form_survey']");
468 $this->assertTrue($this->isTextPresent("'Results' have been saved."),
469 "Status message didn't show up after saving survey!"
470 );
471
472 // Reserve Respondents
473 $this->openCiviPage("survey/search", "reset=1&op=reserve", '_qf_Search_refresh');
474
475 // search for the respondents
476 // select survey
477 $this->select('campaign_survey_id', "label=$surveyTitle");
478
479 // need to wait for Groups field to reload dynamically
480 $this->waitForElementPresent("//select[@id='group']/option[text()='$groupName']");
481
482 // select group
483 $this->click('group');
484 $this->select('group', "label=$groupName");
485 $this->click('_qf_Search_refresh');
486
487 $this->waitForElementPresent('Go');
488 $this->click("CIVICRM_QFID_ts_all_4");
489 $this->select('task', "Reserve Respondents");
490 $this->click("Go");
491 $this->waitForElementPresent('_qf_Reserve_done_reserve-bottom');
492
493 $this->clickLink('_qf_Reserve_done_reserve-bottom', 'access');
494 $this->waitForText('crm-notification-container', "2 Contact\(s\) have been reserved");
495
496 $this->openCiviPage("report/survey/detail", "reset=1", '_qf_SurveyDetails_submit');
497
498 // Select columns to be displayed
499 $this->check('fields[survey_id]');
500 $this->check('fields[survey_response]');
501 $this->select('survey_id_value', "label=$surveyTitle");
502 $this->select('status_id_value', "label=Reserved");
503 $this->click('_qf_SurveyDetails_submit');
504 $this->waitForElementPresent('_qf_SurveyDetails_submit_print');
505 $this->assertTrue($this->isTextPresent("Is equal to Reserved"));
506
507 // commenting out the print assertion as print dialog which appears breaks the webtest
508 // as it is OS-related and cannot be handled through webtest
509
510 // $this->click('_qf_SurveyDetails_submit_print');
511 // $this->waitForPageToLoad($this->getTimeoutMsec());
512
513 // $this->assertTrue($this->isTextPresent("Survey Title = $surveyTitle"));
514 // $this->assertTrue($this->isTextPresent("Q1 = $field1"));
515 // $this->assertTrue($this->isTextPresent("$value1 | $value2 | $value3"));
516
517 // Interview Respondents
518 $this->openCiviPage("survey/search", "reset=1&op=interview", '_qf_Search_refresh');
519
520 // search for the respondents
521 // select survey
522 $this->select('campaign_survey_id', "label=$surveyTitle");
523
524 // need to wait for Groups field to reload dynamically
525 $this->waitForElementPresent("//select[@id='group']/option[text()='$groupName']");
526
527 // select group
528 $this->click('group');
529 $this->select('group', "label=$groupName");
530 //$this->waitForElementPresent("xpath=//ul[@id='crmasmList1']/li");
531 $this->click('_qf_Search_refresh');
532
533 $this->waitForElementPresent('Go');
534 //$this->click("xpath=//*[@class='selector']//tbody//tr[@id='rowid{$id1}']/td[1]");
535 $this->click("mark_x_{$id1}");
536 $this->click("Go");
537 $this->waitForElementPresent('_qf_Interview_cancel_interview');
538
539 $this->type("field_{$id1}_phone-Primary-1", 9876543210);
540 $this->click("//table[@id='voterRecords']/tbody//tr[@id='row_{$id1}']/td[7]/input[2]/../label[text()='$label1']");
541 $this->click("//table[@id='voterRecords']/tbody//tr[@id='row_{$id1}']/td[7]/input[6]/../label[text()='$label2']");
542 $this->select("field_{$id1}_result", $optionLabel1);
543 $this->click("interview_voter_button_{$id1}");
544 // Because it tends to cause problems, all uses of sleep() must be justified in comments
545 // Sleep should never be used for wait for anything to load from the server
546 // Justification for this instance: FIXME
547 sleep(3);
548 // Survey Report
549 $this->openCiviPage("report/survey/detail", "reset=1", '_qf_SurveyDetails_submit');
550
551 // Select columns to be displayed
552 $this->check('fields[survey_id]');
553 $this->check('fields[survey_response]');
554 $this->select('survey_id_value', "label=$surveyTitle");
555 $this->select('status_id_value', "label=Interviewed");
556 $this->click('_qf_SurveyDetails_submit');
557 $this->waitForElementPresent('_qf_SurveyDetails_submit_print');
558 $this->assertTrue($this->isTextPresent("Is equal to Interviewed"));
559
560 // commenting out the print assertion as print dialog which appears breaks the webtest
561 // as it is OS-related and cannot be handled through webtest
562
563 // $this->click('_qf_SurveyDetails_submit_print');
564 // $this->waitForPageToLoad($this->getTimeoutMsec());
565
566 // $this->assertTrue($this->isTextPresent("Survey Title = $surveyTitle"));
567 // $this->assertTrue($this->isTextPresent("Q1 = $field1"));
568 // $this->assertTrue($this->isTextPresent("$value1"));
569
570 // use GOTV (campaign/gotv) to mark the respondents as voted
571 $this->openCiviPage("campaign/gotv", "reset=1");
572
573 // search for the respondents
574 // select survey
575 $this->select('campaign_survey_id', "label=$surveyTitle");
576 // need to wait for Groups field to reload dynamically
577 $this->waitForElementPresent("//select[@id='group']/option[text()='$groupName']");
578
579 // select group
580 $this->click('group');
581 $this->select('group', "label=$groupName");
582 //$this->waitForElementPresent("xpath=//ul[@id='crmasmList1']/li");
583 //$this->click("xpath=//div[@id='search_form_gotv']/div[2]/table/tbody/tr[6]/td/a[text()='Search']");
584 $this->click("link=Search");
585
586 $this->waitForElementPresent("xpath=//table[@id='gotvVoterRecords']/tbody/tr/td[7]");
587 $this->check("xpath=//table[@id='gotvVoterRecords']/tbody/tr/td[7]/input");
588
589 // Check title of the activities created
590 $this->openCiviPage("activity/search", "reset=1", '_qf_Search_refresh');
591 $this->select('activity_survey_id', "label=$surveyTitle");
592 $this->click('_qf_Search_refresh');
593 $this->waitForPageToLoad($this->getTimeoutMsec());
594
595 $this->verifyText("xpath=//table/tbody//tr/td[5]/a[text()='Smith, $firstName1']/../../td[3]",
596 preg_quote("$surveyTitle - Respondent Interview")
597 );
598 $this->verifyText("xpath=//table/tbody//tr/td[5]/a[text()='John, $firstName2']/../../td[3]",
599 preg_quote("$surveyTitle - Respondent Reservation")
600 );
601 }
602 }
603