3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.3 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2013 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
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. |
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. |
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 +--------------------------------------------------------------------+
28 require_once 'CiviTest/CiviSeleniumTestCase.php';
29 require_once 'CRM/Utils/Array.php';
30 class ImportCiviSeleniumTestCase
extends CiviSeleniumTestCase
{
33 * Function to test csv import for each component.
35 * @params string $component component name ( Event, Contribution, Membership, Activity etc)
36 * @params array $headers csv data headers
37 * @params array $rows csv data rows
38 * @params string $contactType contact type
39 * @params string $mode import mode
40 * @params array $fieldMapper select mapper fields while import
41 * @params array $other other parameters
42 * useMappingName : to reuse mapping
43 * dateFormat : date format of data
44 * checkMapperHeaders : to override default check mapper headers
45 * saveMapping : save current mapping?
46 * saveMappingName : to override mapping name
49 function importCSVComponent($component,
52 $contactType = 'Individual',
54 $fieldMapper = array(),
58 // Go to contact import page.
59 $this->open($this->sboxPath
. $this->_getImportComponentUrl($component));
61 $this->waitForPageToLoad($this->getTimeoutMsec());
63 // check for upload field.
64 $this->waitForElementPresent("uploadFile");
66 // Create csv file of sample data.
67 $csvFile = $this->webtestCreateCSV($headers, $rows);
70 $this->webtestAttachFile('uploadFile', $csvFile);
72 // First row is header.
73 $this->click('skipColumnHeader');
75 // select mode, default is 'Skip'.
76 if ($mode == 'Update') {
77 $this->click("CIVICRM_QFID_4_4");
79 elseif ($mode == 'No Duplicate Checking') {
80 $this->click("CIVICRM_QFID_16_6");
83 // select contact type, default is 'Individual'.
84 if ($component != 'Activity') {
85 $contactTypeOption = $this->_getImportComponentContactType($component, $contactType);
86 $this->click($contactTypeOption);
89 // Date format, default: yyyy-mm-dd OR yyyymmdd
90 if (isset($other['dateFormat'])) {
92 $dateFormatMapper = array(
93 'yyyy-mm-dd OR yyyymmdd' => "CIVICRM_QFID_1_14",
94 'mm/dd/yy OR mm-dd-yy' => "CIVICRM_QFID_2_16",
95 'mm/dd/yyyy OR mm-dd-yyyy' => "CIVICRM_QFID_4_18",
96 'Month dd, yyyy' => "CIVICRM_QFID_8_20",
97 'dd-mon-yy OR dd/mm/yy' => "CIVICRM_QFID_16_22",
98 'dd/mm/yyyy' => "CIVICRM_QFID_32_24",
100 $this->click($dateFormatMapper[$other['dateFormat']]);
103 // Use already created mapping
104 $existingMapping = NULL;
105 if (isset($other['useMappingName'])) {
106 $this->select('savedMapping', "label=" . $other['useMappingName']);
107 $existingMapping = $other['useMappingName'];
111 $this->click('_qf_UploadFile_upload');
112 $this->waitForPageToLoad($this->getTimeoutMsec());
114 // Select matching field for cvs data.
115 if (!empty($fieldMapper)) {
116 foreach ($fieldMapper as $field => $value) {
117 $this->select($field, "value={$value}");
121 // Check mapping data.
122 $this->_checkImportMapperData($headers,
125 isset($other['checkMapperHeaders']) ?
$other['checkMapperHeaders'] : array()
129 if (isset($other['saveMapping'])) {
130 $mappingName = isset($other['saveMappingName']) ?
$other['saveMappingName'] : "{$component}Import_" . substr(sha1(rand()), 0, 7);
132 $this->click('saveMapping');
133 $this->type('saveMappingName', $mappingName);
134 $this->type('saveMappingDesc', "Mapping for {$contactType}");
138 $this->click('_qf_MapField_next');
139 $this->waitForElementPresent('_qf_Preview_next-bottom');
141 // Check mapping data.
142 $this->_checkImportMapperData($headers, $rows, $existingMapping, isset($other['checkMapperHeaders']) ?
$other['checkMapperHeaders'] : array());
145 $this->clickLink('_qf_Preview_next-bottom', "_qf_Summary_next");
147 // Check success message.
148 $this->assertTrue($this->isTextPresent("Import has completed successfully. The information below summarizes the results."));
150 // Check summary Details.
151 $importedRecords = count($rows);
152 $checkSummary = array(
153 'Total Rows' => $importedRecords,
154 'Records Imported' => $importedRecords,
157 foreach ($checkSummary as $label => $value) {
158 $this->verifyText("xpath=//table[@id='summary-counts']/tbody/tr/td[text()='{$label}']/following-sibling::td", preg_quote($value));
163 * Function to test contact import.
165 * @params array $headers csv data headers
166 * @params array $rows csv data rows
167 * @params string $contactType contact type
168 * @params string $mode import mode
169 * @params array $fieldMapper select mapper fields while import
170 * @params array $other other parameters
171 * contactSubtype : import for selected Contact Subtype
172 * useMappingName : to reuse mapping
173 * dateFormat : date format of data
174 * checkMapperHeaders : to override default check mapper headers
175 * saveMapping : save current mapping?
176 * saveMappingName : to override mapping name
177 * createGroup : create new group?
178 * createGroupName : to override new Group name
179 * createTag : create new tag?
180 * createTagName : to override new Tag name
181 * selectGroup : select existing group for contacts
182 * selectTag : select existing tag for contacts
183 * callbackImportSummary : function to override default import summary assertions
185 * @params string $type import type (csv/sql)
186 * @todo:currently only supports csv, need to work on sql import
188 function importContacts($headers, $rows, $contactType = 'Individual', $mode = 'Skip', $fieldMapper = array(
189 ), $other = array(), $type = 'csv') {
191 // Go to contact import page.
192 $this->openCiviPage("import/contact", "reset=1", "uploadFile");
194 $originalHeaders = $headers;
195 $originalRows = $rows;
197 // format headers and row to import contacts with relationship data.
198 $this->_formatContactCSVdata($headers, $rows);
200 // Create csv file of sample data.
201 $csvFile = $this->webtestCreateCSV($headers, $rows);
204 $this->webtestAttachFile('uploadFile', $csvFile);
206 // First row is header.
207 $this->click('skipColumnHeader');
209 // select mode, default is 'Skip'.
210 if ($mode == 'Update') {
211 $this->click("CIVICRM_QFID_4_4");
213 elseif ($mode == 'Fill') {
214 $this->click("CIVICRM_QFID_8_6");
216 elseif ($mode == 'No Duplicate Checking') {
217 $this->click("CIVICRM_QFID_16_8");
220 // select contact type, default is 'Individual'.
221 if ($contactType == 'Organization') {
222 $this->click("CIVICRM_QFID_4_14");
224 elseif ($contactType == 'Household') {
225 $this->click("CIVICRM_QFID_2_12");
228 // Select contact subtype
229 if (isset($other['contactSubtype'])) {
230 if ($contactType != 'Individual') {
231 // Because it tends to cause problems, all uses of sleep() must be justified in comments
232 // Sleep should never be used for wait for anything to load from the server
233 // FIXME: this is bad, using sleep to wait for AJAX
234 // Need to use a better way to wait for contact subtypes to repopulate
237 $this->waitForElementPresent("subType");
238 $this->select('subType', 'label=' . $other['contactSubtype']);
241 if (isset($other['dedupe'])) {
242 $this->waitForElementPresent("dedupe");
243 $this->select('dedupe', 'value=' . $other['dedupe']);
246 // Use already created mapping
247 $existingMapping = NULL;
248 if (isset($other['useMappingName'])) {
249 $this->select('savedMapping', "label=" . $other['useMappingName']);
250 $existingMapping = $other['useMappingName'];
253 // Date format, default: yyyy-mm-dd OR yyyymmdd
254 if (isset($other['dateFormat'])) {
256 $dateFormatMapper = array(
257 'yyyy-mm-dd OR yyyymmdd' => "CIVICRM_QFID_1_16",
258 'mm/dd/yy OR mm-dd-yy' => "CIVICRM_QFID_2_18",
259 'mm/dd/yyyy OR mm-dd-yyyy' => "CIVICRM_QFID_4_20",
260 'Month dd, yyyy' => "CIVICRM_QFID_8_22",
261 'dd-mon-yy OR dd/mm/yy' => "CIVICRM_QFID_16_24",
262 'dd/mm/yyyy' => "CIVICRM_QFID_32_26",
264 $this->click($dateFormatMapper[$other['dateFormat']]);
268 $this->click('_qf_DataSource_upload');
269 $this->waitForPageToLoad($this->getTimeoutMsec());
271 if (isset($other['checkMapperHeaders'])) {
272 $checkMapperHeaders = $other['checkMapperHeaders'];
275 $checkMapperHeaders = array(
277 2 => 'Import Data (row 1)',
278 3 => 'Import Data (row 2)',
279 4 => 'Matching CiviCRM Field',
283 // Check mapping data.
284 $this->_checkImportMapperData($headers, $rows, $existingMapping, $checkMapperHeaders, 'td');
286 // Select matching field for cvs data.
287 if (!empty($fieldMapper)) {
288 foreach ($fieldMapper as $field => $value) {
289 $this->select($field, "value={$value}");
294 if (isset($other['saveMapping'])) {
295 $mappingName = isset($other['saveMappingName']) ?
$other['saveMappingName'] : 'ContactImport_' . substr(sha1(rand()), 0, 7);
296 $this->click('saveMapping');
297 $this->type('saveMappingName', $mappingName);
298 $this->type('saveMappingDesc', "Mapping for {$contactType}");
302 $this->click('_qf_MapField_next');
303 $this->waitForPageToLoad($this->getTimeoutMsec());
305 // Check mapping data.
306 $this->_checkImportMapperData($headers, $rows, $existingMapping, $checkMapperHeaders, 'td');
308 // Add imported contacts in new group.
310 $existingGroups = array();
311 if (isset($other['createGroup'])) {
312 $groupName = isset($other['createGroupName']) ?
$other['createGroupName'] : 'ContactImport_' . substr(sha1(rand()), 0, 7);
314 $this->click("css=#new-group div.crm-accordion-header");
315 $this->type('newGroupName', $groupName);
316 $this->type('newGroupDesc', "Group For {$contactType}");
318 if (isset($other['selectGroup'])) {
319 // reuse existing groups.
320 if (is_array($other['selectGroup'])) {
321 foreach ($other['selectGroup'] as $existingGroup) {
322 $this->select('groups[]', 'label=' . $existingGroup);
323 $existingGroups[] = $existingGroup;
327 $this->select('groups[]', 'label=' . $other['selectGroup']);
328 $existingGroups[] = $other['selectGroup'];
332 // Assign new tag to the imported contacts.
334 $existingTags = array();
335 if (isset($other['createTag'])) {
336 $tagName = isset($other['createTagName']) ?
$other['createTagName'] : "{$contactType}_" . substr(sha1(rand()), 0, 7);
338 $this->click("css=#new-tag div.crm-accordion-header");
339 $this->type('newTagName', $tagName);
340 $this->type('newTagDesc', "Tag for {$contactType}");
342 if (isset($other['selectTag'])) {
343 $this->click("css=#existing-tags div.crm-accordion-header");
344 // reuse existing tags.
345 if (is_array($other['selectTag'])) {
346 foreach ($other['selectTag'] as $existingTag) {
347 $this->click("xpath=//div[@id='existing-tags']//div[@class='crm-accordion-body']//label[text()='{$existingTag}']");
348 $existingTags[] = $existingTag;
352 $this->click("xpath=//div[@id='existing-tags']//div[@class='crm-accordion-body']//label[text()='" . $other['selectTag'] . "']");
353 $existingTags[] = $other['selectTag'];
358 $this->click('_qf_Preview_next');
359 $this->waitForPageToLoad($this->getTimeoutMsec());
361 // Check confirmation alert.
362 $this->assertTrue((bool)preg_match("/^Are you sure you want to Import now[\s\S]$/", $this->getConfirmation()));
363 $this->chooseOkOnNextConfirmation();
364 $this->waitForPageToLoad($this->getTimeoutMsec());
366 // Visit summary page.
367 $this->waitForElementPresent("_qf_Summary_next");
369 // Check success message.
370 $this->assertTrue($this->isTextPresent("Import has completed successfully. The information below summarizes the results."));
372 // Check summary Details.
373 $importedContacts = $totalRows = count($originalRows);
375 // Include relationships contacts ( if exists )
376 if (isset($originalHeaders['contact_relationships']) && is_array($originalHeaders['contact_relationships'])) {
377 foreach ($originalRows as $row) {
378 $importedContacts +
= count($row['contact_relationships']);
382 $importedContactsCount = ($importedContacts == 1) ?
'One contact' : "$importedContacts contacts";
383 $taggedContactsCount = ($importedContacts == 1) ?
'One contact is' : "$importedContacts contacts are";
384 $checkSummary = array(
385 'Total Rows' => $totalRows,
386 'Total Contacts' => $importedContacts,
390 $checkSummary['Import to Groups'] = "{$groupName}: {$importedContactsCount} added to this new group.";
394 $checkSummary['Tagged Imported Contacts'] = "{$tagName}: {$taggedContactsCount} tagged with this tag.";
397 if ($existingGroups) {
398 if (!isset($checkSummary['Import to Groups'])) {
399 $checkSummary['Import to Groups'] = '';
401 foreach ($existingGroups as $existingGroup) {
402 $checkSummary['Import to Groups'] .= "{$existingGroup}: {$importedContactsCount} added to this existing group.";
407 if (!isset($checkSummary['Tagged Imported Contacts'])) {
408 $checkSummary['Tagged Imported Contacts'] = '';
410 foreach ($existingTags as $existingTag) {
411 $checkSummary['Tagged Imported Contacts'] .= "{$existingTag}: {$taggedContactsCount} tagged with this tag.";
415 if (!empty($other['callbackImportSummary']) && is_callable(array(
416 $this, $other['callbackImportSummary']))) {
417 $callbackImportSummary = $other['callbackImportSummary'];
418 $this->$callbackImportSummary($originalHeaders, $originalRows, $checkSummary);
421 foreach ($checkSummary as $label => $value) {
422 $this->verifyText("xpath=//table[@id='summary-counts']/tbody/tr/td[text()='{$label}']/following-sibling::td", preg_quote($value));
428 * Helper function to get the import url of the component.
430 * @params string $component component name
432 * @return string import url
434 function _getImportComponentUrl($component) {
436 $importComponentUrl = array(
437 'Event' => 'civicrm/event/import?reset=1',
438 'Contribution' => 'civicrm/contribute/import?reset=1',
439 'Membership' => 'civicrm/member/import?reset=1',
440 'Activity' => 'civicrm/import/activity?reset=1',
443 return $importComponentUrl[$component];
447 * Helper function to get the import url of the component.
449 * @params string $component component name
451 * @return string import url
453 function _getImportComponentContactType($component, $contactType) {
454 $importComponentMode = array(
455 'Event' => array('Individual' => 'CIVICRM_QFID_1_8',
456 'Household' => 'CIVICRM_QFID_2_10',
457 'Organization' => 'CIVICRM_QFID_4_12',
459 'Contribution' => array(
460 'Individual' => 'CIVICRM_QFID_1_6',
461 'Household' => 'CIVICRM_QFID_2_8',
462 'Organization' => 'CIVICRM_QFID_4_10',
464 'Membership' => array(
465 'Individual' => 'CIVICRM_QFID_1_6',
466 'Household' => 'CIVICRM_QFID_2_8',
467 'Organization' => 'CIVICRM_QFID_4_10',
471 return $importComponentMode[$component][$contactType];
475 * Helper function to check import mapping fields.
477 * @params array $headers field headers
478 * @params array $rows field rows
479 * @params array $checkMapperHeaders override default mapper headers
481 function _checkImportMapperData($headers, $rows, $existingMapping = NULL, $checkMapperHeaders = array(
482 ), $headerSelector = 'th') {
484 if (empty($checkMapperHeaders)) {
485 $checkMapperHeaders = array(
486 1 => 'Column Headers',
487 2 => 'Import Data (row 2)',
488 3 => 'Import Data (row 3)',
489 4 => 'Matching CiviCRM Field',
494 if ($existingMapping) {
495 $this->verifyText("xpath=//div[@id='map-field']//table[1]/tbody/tr[{$rowNumber}]/th[1]", preg_quote("Saved Field Mapping: {$existingMapping}"));
499 foreach ($checkMapperHeaders as $rownum => $value) {
500 $this->verifyText("xpath=//div[@id='map-field']//table[1]/tbody/tr[{$rowNumber}]/{$headerSelector}[{$rownum}]", preg_quote($value));
504 foreach ($headers as $field => $header) {
505 $this->verifyText("xpath=//div[@id='map-field']//table[1]/tbody/tr[{$rowNumber}]/td[1]", preg_quote($header));
507 foreach ($rows as $row) {
508 $this->verifyText("xpath=//div[@id='map-field']//table[1]/tbody/tr[{$rowNumber}]/td[{$colnum}]", preg_quote($row[$field]));
516 * Helper function to get imported contact ids.
518 * @params array $rows fields rows
519 * @params string $contactType contact type
521 * @return array $contactIds imported contact ids
523 function _getImportedContactIds($rows, $contactType = 'Individual') {
524 $contactIds = array();
526 foreach ($rows as $row) {
529 // Build search name.
530 if ($contactType == 'Individual') {
531 $searchName = "{$row['last_name']}, {$row['first_name']}";
533 elseif ($contactType == 'Organization') {
534 $searchName = $row['organization_name'];
536 elseif ($contactType == 'Household') {
537 $searchName = $row['household_name'];
540 $this->openCiviPage("dashboard", "reset=1");
542 // Type search name in autocomplete.
543 $this->click("css=input#sort_name_navigation");
544 $this->type("css=input#sort_name_navigation", $searchName);
545 $this->typeKeys("css=input#sort_name_navigation", $searchName);
547 // Wait for result list.
548 $this->waitForElementPresent("css=div.ac_results-inner li");
550 // Visit contact summary page.
551 $this->click("css=div.ac_results-inner li");
552 $this->waitForPageToLoad($this->getTimeoutMsec());
554 // Get contact id from url.
556 preg_match('/cid=([0-9]+)/', $this->getLocation(), $matches);
557 $contactIds[] = $matches[1];
564 * Helper function to format headers and rows for contact relationship data.
566 * @params array $headers data headers
567 * @params string $rows data rows
569 function _formatContactCSVdata(&$headers, &$rows) {
570 if (!isset($headers['contact_relationships'])) {
574 $relationshipHeaders = $headers['contact_relationships'];
575 unset($headers['contact_relationships']);
577 if (empty($relationshipHeaders) ||
!is_array($relationshipHeaders)) {
581 foreach ($relationshipHeaders as $relationshipHeader) {
582 $headers = array_merge($headers, $relationshipHeader);
585 foreach ($rows as & $row) {
586 $relationshipRows = $row['contact_relationships'];
587 unset($row['contact_relationships']);
588 foreach ($relationshipRows as $relationshipRow) {
589 $row = array_merge($row, $relationshipRow);