diff --git a/app/code/Magento/Catalog/Block/Product/Widget/NewWidget.php b/app/code/Magento/Catalog/Block/Product/Widget/NewWidget.php
index 88d487aea32c98e1f15c8129d288006acacafb13..1a8a421f0f2bcad446a6f5d73556836925804122 100644
--- a/app/code/Magento/Catalog/Block/Product/Widget/NewWidget.php
+++ b/app/code/Magento/Catalog/Block/Product/Widget/NewWidget.php
@@ -32,6 +32,8 @@ class NewWidget extends \Magento\Catalog\Block\Product\NewProduct implements \Ma
 
     /**
      * Name of request parameter for page number value
+     *
+     * @deprecated
      */
     const PAGE_VAR_NAME = 'np';
 
@@ -88,7 +90,7 @@ class NewWidget extends \Magento\Catalog\Block\Product\NewProduct implements \Ma
      */
     public function getCurrentPage()
     {
-        return abs((int)$this->getRequest()->getParam(self::PAGE_VAR_NAME));
+        return abs((int)$this->getRequest()->getParam($this->getData('page_var_name')));
     }
 
     /**
@@ -103,7 +105,8 @@ class NewWidget extends \Magento\Catalog\Block\Product\NewProduct implements \Ma
             [
                 $this->getDisplayType(),
                 $this->getProductsPerPage(),
-                intval($this->getRequest()->getParam(self::PAGE_VAR_NAME))
+                intval($this->getRequest()->getParam($this->getData('page_var_name'), 1)),
+                serialize($this->getRequest()->getParams())
             ]
         );
     }
@@ -187,7 +190,7 @@ class NewWidget extends \Magento\Catalog\Block\Product\NewProduct implements \Ma
                 $this->_pager->setUseContainer(true)
                     ->setShowAmounts(true)
                     ->setShowPerPage(false)
-                    ->setPageVarName(self::PAGE_VAR_NAME)
+                    ->setPageVarName($this->getData('page_var_name'))
                     ->setLimit($this->getProductsPerPage())
                     ->setTotalLimit($this->getProductsCount())
                     ->setCollection($this->getProductCollection());
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/SuggestAttributeSets.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/SuggestAttributeSets.php
index 456a579c6191d9a284e0cf7f5f16a99d38e317d3..9b1a8c28bb17d6b57edd5c04b9923f1a9d28fb44 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/SuggestAttributeSets.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/SuggestAttributeSets.php
@@ -8,6 +8,13 @@ namespace Magento\Catalog\Controller\Adminhtml\Product;
 
 class SuggestAttributeSets extends \Magento\Backend\App\Action
 {
+    /**
+     * Authorization level of a basic admin session
+     *
+     * @see _isAllowed()
+     */
+    const ADMIN_RESOURCE = 'Magento_Catalog::sets';
+    
     /**
      * @var \Magento\Framework\Controller\Result\JsonFactory
      */
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/SuggestAttributes.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/SuggestAttributes.php
index 6b60c4f74810fc5cc445384205066ba752efe4a5..7047334f046e08bb7a6f4de737e8be40df76924a 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/SuggestAttributes.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/SuggestAttributes.php
@@ -6,8 +6,15 @@
  */
 namespace Magento\Catalog\Controller\Adminhtml\Product;
 
-class SuggestAttributes extends \Magento\Catalog\Controller\Adminhtml\Product
+class SuggestAttributes extends \Magento\Backend\App\Action
 {
+    /**
+     * Authorization level of a basic admin session
+     *
+     * @see _isAllowed()
+     */
+    const ADMIN_RESOURCE = 'Magento_Catalog::attributes_attributes';
+
     /**
      * @var \Magento\Framework\Controller\Result\JsonFactory
      */
@@ -20,17 +27,15 @@ class SuggestAttributes extends \Magento\Catalog\Controller\Adminhtml\Product
 
     /**
      * @param \Magento\Backend\App\Action\Context $context
-     * @param \Magento\Catalog\Controller\Adminhtml\Product\Builder $productBuilder
      * @param \Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory
      * @param \Magento\Framework\View\LayoutFactory $layoutFactory
      */
     public function __construct(
         \Magento\Backend\App\Action\Context $context,
-        \Magento\Catalog\Controller\Adminhtml\Product\Builder $productBuilder,
         \Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory,
         \Magento\Framework\View\LayoutFactory $layoutFactory
     ) {
-        parent::__construct($context, $productBuilder);
+        parent::__construct($context);
         $this->resultJsonFactory = $resultJsonFactory;
         $this->layoutFactory = $layoutFactory;
     }
diff --git a/app/code/Magento/Catalog/Test/Unit/Block/Product/Widget/NewWidgetTest.php b/app/code/Magento/Catalog/Test/Unit/Block/Product/Widget/NewWidgetTest.php
index 9cd7790436de6419fc08f9533d386ab1250870fc..f21945f588abb5c34fbcea2411abc3bed758aca0 100644
--- a/app/code/Magento/Catalog/Test/Unit/Block/Product/Widget/NewWidgetTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Block/Product/Widget/NewWidgetTest.php
@@ -144,9 +144,11 @@ class NewWidgetTest extends \PHPUnit_Framework_TestCase
      */
     public function testGetCurrentPage($pageNumber, $expectedResult)
     {
+        $this->block->setData('page_var_name', 'page_number');
+
         $this->requestMock->expects($this->any())
             ->method('getParam')
-            ->with(\Magento\Catalog\Block\Product\Widget\NewWidget::PAGE_VAR_NAME)
+            ->with('page_number')
             ->willReturn($pageNumber);
 
         $this->assertEquals($expectedResult, $this->block->getCurrentPage());
diff --git a/app/code/Magento/Catalog/view/adminhtml/ui_component/product_listing.xml b/app/code/Magento/Catalog/view/adminhtml/ui_component/product_listing.xml
index 8f6792b862d2ce93c9e14e0086977ff83a3dd16f..1274db9598ebc6e7a12d0c3b6d0946dc30ce3ee3 100644
--- a/app/code/Magento/Catalog/view/adminhtml/ui_component/product_listing.xml
+++ b/app/code/Magento/Catalog/view/adminhtml/ui_component/product_listing.xml
@@ -234,9 +234,8 @@
             <argument name="data" xsi:type="array">
                 <item name="options" xsi:type="object">Magento\Store\Model\ResourceModel\Website\Collection</item>
                 <item name="config" xsi:type="array">
-                    <item name="component" xsi:type="string">Magento_Ui/js/grid/columns/select</item>
                     <item name="add_field" xsi:type="boolean">true</item>
-                    <item name="dataType" xsi:type="string">select</item>
+                    <item name="dataType" xsi:type="string">text</item>
                     <item name="label" xsi:type="string" translate="true">Websites</item>
                     <item name="sortOrder" xsi:type="number">100</item>
                 </item>
diff --git a/app/code/Magento/CatalogImportExport/Model/Export/Product.php b/app/code/Magento/CatalogImportExport/Model/Export/Product.php
index 0fbda9d526f0b8d35155a0070a31bd38950c28bb..2eecefb43346f4b303a7d71c54796bd8db162661 100644
--- a/app/code/Magento/CatalogImportExport/Model/Export/Product.php
+++ b/app/code/Magento/CatalogImportExport/Model/Export/Product.php
@@ -695,8 +695,11 @@ class Product extends \Magento\ImportExport\Model\Export\Entity\AbstractEntity
                 [],
                 [
                     'related_skus',
+                    'related_position',
                     'crosssell_skus',
+                    'crosssell_position',
                     'upsell_skus',
+                    'upsell_position'
                 ],
                 ['additional_images', 'additional_image_labels', 'hide_from_product_page']
             );
@@ -1113,6 +1116,8 @@ class Product extends \Magento\ImportExport\Model\Export\Entity\AbstractEntity
                     asort($associations);
                     $dataRow[$colPrefix . 'skus'] =
                         implode(Import::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR, array_keys($associations));
+                    $dataRow[$colPrefix . 'position'] =
+                        implode(Import::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR, array_values($associations));
                 }
             }
             $dataRow = $this->rowCustomizer->addData($dataRow, $productId);
diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product.php b/app/code/Magento/CatalogImportExport/Model/Import/Product.php
index 140c4f3596ae58cddece531f1bbc5ee7d7410153..4e22f7ed32a8767bd022fef826f886eac3cfb9a4 100644
--- a/app/code/Magento/CatalogImportExport/Model/Import/Product.php
+++ b/app/code/Magento/CatalogImportExport/Model/Import/Product.php
@@ -294,8 +294,11 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
         'max_sale_qty' => 'max_cart_qty',
         'notify_stock_qty' => 'notify_on_stock_below',
         '_related_sku' => 'related_skus',
+        '_related_position' => 'related_position',
         '_crosssell_sku' => 'crosssell_skus',
+        '_crosssell_position' => 'crosssell_position',
         '_upsell_sku' => 'upsell_skus',
+        '_upsell_position' => 'upsell_position',
         'meta_keyword' => 'meta_keywords',
     ];
 
@@ -1102,13 +1105,27 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
 
                 $sku = $rowData[self::COL_SKU];
 
+                $productId = $this->skuProcessor->getNewSku($sku)[$this->getProductEntityLinkField()];
+                $productLinkKeys = [];
+                $select = $this->_connection->select()->from(
+                    $resource->getTable('catalog_product_link'),
+                    ['id' => 'link_id', 'linked_id' => 'linked_product_id', 'link_type_id' => 'link_type_id']
+                )->where(
+                    'product_id = :product_id'
+                );
+                $bind = [':product_id' => $productId];
+                foreach ($this->_connection->fetchAll($select, $bind) as $linkData) {
+                    $linkKey = "{$productId}-{$linkData['linked_id']}-{$linkData['link_type_id']}";
+                    $productLinkKeys[$linkKey] = $linkData['id'];
+                }
                 foreach ($this->_linkNameToId as $linkName => $linkId) {
-                    $productId = $this->skuProcessor->getNewSku($sku)[$this->getProductEntityLinkField()];
                     $productIds[] = $productId;
                     if (isset($rowData[$linkName . 'sku'])) {
                         $linkSkus = explode($this->getMultipleValueSeparator(), $rowData[$linkName . 'sku']);
-
-                        foreach ($linkSkus as $linkedSku) {
+                        $linkPositions = !empty($rowData[$linkName . 'position'])
+                            ? explode($this->getMultipleValueSeparator(), $rowData[$linkName . 'position'])
+                            : [];
+                        foreach ($linkSkus as $linkedKey => $linkedSku) {
                             $linkedSku = trim($linkedSku);
                             if ((!is_null(
                                         $this->skuProcessor->getNewSku($linkedSku)
@@ -1143,19 +1160,21 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
                                 }
 
                                 $linkKey = "{$productId}-{$linkedId}-{$linkId}";
-
+                                if(empty($productLinkKeys[$linkKey])) {
+                                    $productLinkKeys[$linkKey] = $nextLinkId;
+                                }
                                 if (!isset($linkRows[$linkKey])) {
                                     $linkRows[$linkKey] = [
-                                        'link_id' => $nextLinkId,
+                                        'link_id' => $productLinkKeys[$linkKey],
                                         'product_id' => $productId,
                                         'linked_product_id' => $linkedId,
                                         'link_type_id' => $linkId,
                                     ];
-                                    if (!empty($rowData[$linkName . 'position'])) {
+                                    if (!empty($linkPositions[$linkedKey])) {
                                         $positionRows[] = [
-                                            'link_id' => $nextLinkId,
+                                            'link_id' => $productLinkKeys[$linkKey],
                                             'product_link_attribute_id' => $positionAttrId[$linkId],
-                                            'value' => $rowData[$linkName . 'position'],
+                                            'value' => $linkPositions[$linkedKey],
                                         ];
                                     }
                                     $nextLinkId++;
@@ -1176,7 +1195,10 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
             }
             if ($positionRows) {
                 // process linked product positions
-                $this->_connection->insertOnDuplicate($resource->getAttributeTypeTable('int'), $positionRows, ['value']);
+                $this->_connection->insertOnDuplicate(
+                    $resource->getAttributeTypeTable('int'),
+                    $positionRows, ['value']
+                );
             }
         }
         return $this;
diff --git a/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php b/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php
index bcabe87fc0dfd63454eb96dca7fc782cee4876bf..ae734106be63a6395b7fb415a95590d43639e1fa 100644
--- a/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php
+++ b/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php
@@ -51,6 +51,9 @@ class AfterImportDataObserver implements ObserverInterface
     /** @var \Magento\CatalogUrlRewrite\Model\ObjectRegistry */
     protected $productCategories;
 
+    /** @var UrlFinderInterface */
+    protected $urlFinder;
+
     /** @var \Magento\Store\Model\StoreManagerInterface */
     protected $storeManager;
 
@@ -353,14 +356,10 @@ class AfterImportDataObserver implements ObserverInterface
             if ($category === false) {
                 continue;
             }
-            if ($currentUrlRewrite->getIsAutogenerated()) {
-                $urlRewrite = $this->generateForAutogenerated($currentUrlRewrite, $category);
-            } else {
-                $urlRewrite = $this->generateForCustom($currentUrlRewrite, $category);
-            }
-            if ($urlRewrite) {
-                $urlRewrites[] = $urlRewrite;
-            }
+            $url = $currentUrlRewrite->getIsAutogenerated()
+                ? $this->generateForAutogenerated($currentUrlRewrite, $category)
+                : $this->generateForCustom($currentUrlRewrite, $category);
+            $urlRewrites = array_merge($urlRewrites, $url);
         }
 
         $this->product = null;
diff --git a/app/code/Magento/CatalogWidget/Block/Product/ProductsList.php b/app/code/Magento/CatalogWidget/Block/Product/ProductsList.php
index 1d4d77943dca1b009403a774e6b3b9a6152d26f7..7087a928b4ced70659bc229bf6a2446d415394e5 100644
--- a/app/code/Magento/CatalogWidget/Block/Product/ProductsList.php
+++ b/app/code/Magento/CatalogWidget/Block/Product/ProductsList.php
@@ -22,6 +22,8 @@ class ProductsList extends \Magento\Catalog\Block\Product\AbstractProduct implem
 
     /**
      * Name of request parameter for page number value
+     *
+     * @deprecated
      */
     const PAGE_VAR_NAME = 'np';
 
@@ -142,9 +144,10 @@ class ProductsList extends \Magento\Catalog\Block\Product\AbstractProduct implem
             $this->_storeManager->getStore()->getId(),
             $this->_design->getDesignTheme()->getId(),
             $this->httpContext->getValue(\Magento\Customer\Model\Context::CONTEXT_GROUP),
-            intval($this->getRequest()->getParam(self::PAGE_VAR_NAME, 1)),
+            intval($this->getRequest()->getParam($this->getData('page_var_name'), 1)),
             $this->getProductsPerPage(),
-            $conditions
+            $conditions,
+            serialize($this->getRequest()->getParams())
         ];
     }
 
@@ -208,7 +211,7 @@ class ProductsList extends \Magento\Catalog\Block\Product\AbstractProduct implem
         $collection = $this->_addProductAttributesAndPrices($collection)
             ->addStoreFilter()
             ->setPageSize($this->getPageSize())
-            ->setCurPage($this->getRequest()->getParam(self::PAGE_VAR_NAME, 1));
+            ->setCurPage($this->getRequest()->getParam($this->getData('page_var_name'), 1));
 
         $conditions = $this->getConditions();
         $conditions->collectValidatedAttributes($collection);
@@ -305,7 +308,7 @@ class ProductsList extends \Magento\Catalog\Block\Product\AbstractProduct implem
                 $this->pager->setUseContainer(true)
                     ->setShowAmounts(true)
                     ->setShowPerPage(false)
-                    ->setPageVarName(self::PAGE_VAR_NAME)
+                    ->setPageVarName($this->getData('page_var_name'))
                     ->setLimit($this->getProductsPerPage())
                     ->setTotalLimit($this->getProductsCount())
                     ->setCollection($this->getProductCollection());
diff --git a/app/code/Magento/CatalogWidget/Test/Unit/Block/Product/ProductsListTest.php b/app/code/Magento/CatalogWidget/Test/Unit/Block/Product/ProductsListTest.php
index 4ba2358bc00cfac60f4f27d790f70c16069c3649..1b7d27a638952c2cc79a2b030c1b87df33e13c3c 100644
--- a/app/code/Magento/CatalogWidget/Test/Unit/Block/Product/ProductsListTest.php
+++ b/app/code/Magento/CatalogWidget/Test/Unit/Block/Product/ProductsListTest.php
@@ -126,9 +126,21 @@ class ProductsListTest extends \PHPUnit_Framework_TestCase
         $this->httpContext->expects($this->once())->method('getValue')->willReturn('context_group');
         $this->productsList->setData('conditions', 'some_serialized_conditions');
 
-        $this->request->expects($this->once())->method('getParam')->with('np')->willReturn(1);
-
-        $cacheKey = ['CATALOG_PRODUCTS_LIST_WIDGET', 1, 'blank', 'context_group', 1, 5, 'some_serialized_conditions'];
+        $this->productsList->setData('page_var_name', 'page_number');
+        $this->request->expects($this->once())->method('getParam')->with('page_number')->willReturn(1);
+
+        $this->request->expects($this->once())->method('getParams')->willReturn('request_params');
+
+        $cacheKey = [
+            'CATALOG_PRODUCTS_LIST_WIDGET',
+            1,
+            'blank',
+            'context_group',
+            1,
+            5,
+            'some_serialized_conditions',
+            serialize('request_params')
+        ];
         $this->assertEquals($cacheKey, $this->productsList->getCacheKeyInfo());
     }
 
@@ -264,7 +276,6 @@ class ProductsListTest extends \PHPUnit_Framework_TestCase
         $this->rule->expects($this->once())->method('loadPost')->willReturnSelf();
         $this->rule->expects($this->once())->method('getConditions')->willReturn($conditions);
 
-
         if ($productsPerPage) {
             $this->productsList->setData('products_per_page', $productsPerPage);
         } else {
diff --git a/app/code/Magento/Review/Model/Review.php b/app/code/Magento/Review/Model/Review.php
index 792babfba4e3203a536054c4555cbc48471edd64..007910d8fbc5b5555fbeb98b11eaee9cc84b5f6b 100644
--- a/app/code/Magento/Review/Model/Review.php
+++ b/app/code/Magento/Review/Model/Review.php
@@ -374,7 +374,7 @@ class Review extends \Magento\Framework\Model\AbstractModel implements IdentityI
     public function getIdentities()
     {
         $tags = [];
-        if ($this->isApproved() && $this->getEntityPkValue()) {
+        if ($this->getEntityPkValue()) {
             $tags[] = Product::CACHE_TAG . '_' . $this->getEntityPkValue();
         }
         return $tags;
diff --git a/app/code/Magento/Review/Test/Unit/Model/ReviewTest.php b/app/code/Magento/Review/Test/Unit/Model/ReviewTest.php
index 0f12dda793e03dea7208c7b67524897c9db877db..7eaca296843c74660ebe72eb2776339a334a3522 100644
--- a/app/code/Magento/Review/Test/Unit/Model/ReviewTest.php
+++ b/app/code/Magento/Review/Test/Unit/Model/ReviewTest.php
@@ -278,8 +278,16 @@ class ReviewTest extends \PHPUnit_Framework_TestCase
         $this->assertEmpty($this->review->getIdentities());
 
         $productId = 1;
+        $this->review->setEntityPkValue($productId);
+        $this->review->setStatusId(Review::STATUS_PENDING);
+        $this->assertEquals([Product::CACHE_TAG . '_' . $productId], $this->review->getIdentities());
+
         $this->review->setEntityPkValue($productId);
         $this->review->setStatusId(Review::STATUS_APPROVED);
         $this->assertEquals([Product::CACHE_TAG . '_' . $productId], $this->review->getIdentities());
+
+        $this->review->setEntityPkValue($productId);
+        $this->review->setStatusId(Review::STATUS_NOT_APPROVED);
+        $this->assertEquals([Product::CACHE_TAG . '_' . $productId], $this->review->getIdentities());
     }
 }
diff --git a/app/code/Magento/Swatches/Block/Product/Renderer/Listing/Configurable.php b/app/code/Magento/Swatches/Block/Product/Renderer/Listing/Configurable.php
index fc7b1c5d35893ae65bf27ae2f2b81defd05adcc0..b9abf8fcab21bf660e52b6455c5209a839d4d297 100644
--- a/app/code/Magento/Swatches/Block/Product/Renderer/Listing/Configurable.php
+++ b/app/code/Magento/Swatches/Block/Product/Renderer/Listing/Configurable.php
@@ -39,6 +39,7 @@ class Configurable extends \Magento\Swatches\Block\Product\Renderer\Configurable
      */
     protected function getSwatchAttributesData()
     {
+        $result = [];
         $swatchAttributeData = parent::getSwatchAttributesData();
         foreach ($swatchAttributeData as $attributeId => $item) {
             if (!empty($item['used_in_product_listing'])) {
diff --git a/app/code/Magento/Swatches/Model/Plugin/EavAttribute.php b/app/code/Magento/Swatches/Model/Plugin/EavAttribute.php
index fddd3ec78ed922631d03be2653ba86a96cfabb79..c56e732cd9bce47770af7f15bead71ac5336d540 100644
--- a/app/code/Magento/Swatches/Model/Plugin/EavAttribute.php
+++ b/app/code/Magento/Swatches/Model/Plugin/EavAttribute.php
@@ -174,7 +174,7 @@ class EavAttribute
     {
         if (isset($optionsArray['value']) && is_array($optionsArray['value'])) {
             foreach (array_keys($optionsArray['value']) as $optionId) {
-                if ($optionsArray['delete'][$optionId] == 1) {
+                if (isset($optionsArray['delete']) && $optionsArray['delete'][$optionId] == 1) {
                     unset($optionsArray['value'][$optionId]);
                 }
             }
diff --git a/app/code/Magento/Swatches/Test/Unit/Block/Product/Renderer/Listing/ConfigurableTest.php b/app/code/Magento/Swatches/Test/Unit/Block/Product/Renderer/Listing/ConfigurableTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..9b24e6b79658b6a117406ae20d6085e2832729d2
--- /dev/null
+++ b/app/code/Magento/Swatches/Test/Unit/Block/Product/Renderer/Listing/ConfigurableTest.php
@@ -0,0 +1,217 @@
+<?php
+/**
+ * Copyright © 2015 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Swatches\Test\Unit\Block\Product\Renderer\Listing;
+
+use Magento\Swatches\Block\Product\Renderer\Configurable;
+
+/**
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ */
+class ConfigurableTest extends \PHPUnit_Framework_TestCase
+{
+    /** @var Configurable */
+    private $configurable;
+
+    /** @var \Magento\Framework\Stdlib\ArrayUtils|\PHPUnit_Framework_MockObject_MockObject */
+    private $arrayUtils;
+
+    /** @var \Magento\Framework\Json\EncoderInterface|\PHPUnit_Framework_MockObject_MockObject */
+    private $jsonEncoder;
+
+    /** @var \Magento\ConfigurableProduct\Helper\Data|\PHPUnit_Framework_MockObject_MockObject */
+    private $helper;
+
+    /** @var \Magento\Swatches\Helper\Data|\PHPUnit_Framework_MockObject_MockObject */
+    private $swatchHelper;
+
+    /** @var \Magento\Swatches\Helper\Media|\PHPUnit_Framework_MockObject_MockObject */
+    private $swatchMediaHelper;
+
+    /** @var \Magento\Catalog\Helper\Product|\PHPUnit_Framework_MockObject_MockObject */
+    private $catalogProduct;
+
+    /** @var \Magento\Customer\Helper\Session\CurrentCustomer|\PHPUnit_Framework_MockObject_MockObject */
+    private $currentCustomer;
+
+    /** @var \Magento\Framework\Pricing\PriceCurrencyInterface|\PHPUnit_Framework_MockObject_MockObject */
+    private $priceCurrency;
+
+    /** @var \Magento\ConfigurableProduct\Model\ConfigurableAttributeData|\PHPUnit_Framework_MockObject_MockObject */
+    private $configurableAttributeData;
+
+    /** @var \Magento\Catalog\Model\Product|\PHPUnit_Framework_MockObject_MockObject */
+    private $product;
+
+    /** @var \Magento\Catalog\Model\Product\Type\AbstractType|\PHPUnit_Framework_MockObject_MockObject */
+    private $typeInstance;
+
+    /** @var \Magento\Framework\App\Config\ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject */
+    private $scopeConfig;
+
+    /** @var \Magento\Catalog\Helper\Image|\PHPUnit_Framework_MockObject_MockObject */
+    private $imageHelper;
+
+    /** @var \Magento\Framework\UrlInterface|\PHPUnit_Framework_MockObject_MockObject  */
+    private $urlBuilder;
+
+    public function setUp()
+    {
+        $this->arrayUtils = $this->getMock('\Magento\Framework\Stdlib\ArrayUtils', [], [], '', false);
+        $this->jsonEncoder = $this->getMock('\Magento\Framework\Json\EncoderInterface', [], [], '', false);
+        $this->helper = $this->getMock('\Magento\ConfigurableProduct\Helper\Data', [], [], '', false);
+        $this->swatchHelper = $this->getMock('\Magento\Swatches\Helper\Data', [], [], '', false);
+        $this->swatchMediaHelper = $this->getMock('\Magento\Swatches\Helper\Media', [], [], '', false);
+        $this->catalogProduct = $this->getMock('\Magento\Catalog\Helper\Product', [], [], '', false);
+        $this->currentCustomer = $this->getMock('\Magento\Customer\Helper\Session\CurrentCustomer', [], [], '', false);
+        $this->priceCurrency = $this->getMock('\Magento\Framework\Pricing\PriceCurrencyInterface', [], [], '', false);
+        $this->configurableAttributeData = $this->getMock(
+            'Magento\ConfigurableProduct\Model\ConfigurableAttributeData',
+            [],
+            [],
+            '',
+            false
+        );
+        $this->product = $this->getMock('\Magento\Catalog\Model\Product', [], [], '', false);
+        $this->typeInstance = $this->getMock('\Magento\Catalog\Model\Product\Type\AbstractType', [], [], '', false);
+        $this->scopeConfig = $this->getMock('\Magento\Framework\App\Config\ScopeConfigInterface', [], [], '', false);
+        $this->imageHelper = $this->getMock('\Magento\Catalog\Helper\Image', [], [], '', false);
+        $this->urlBuilder = $this->getMock('\Magento\Framework\UrlInterface');
+
+        $objectManagerHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
+        $this->configurable = $objectManagerHelper->getObject(
+            '\Magento\Swatches\Block\Product\Renderer\Listing\Configurable',
+            [
+                'scopeConfig' => $this->scopeConfig,
+                'imageHelper' => $this->imageHelper,
+                'urlBuilder' => $this->urlBuilder,
+                'arrayUtils' => $this->arrayUtils,
+                'jsonEncoder' => $this->jsonEncoder,
+                'helper' => $this->helper,
+                'swatchHelper' => $this->swatchHelper,
+                'swatchMediaHelper' => $this->swatchMediaHelper,
+                'catalogProduct' => $this->catalogProduct,
+                'currentCustomer' => $this->currentCustomer,
+                'priceCurrency' => $this->priceCurrency,
+                'configurableAttributeData' => $this->configurableAttributeData,
+                'data' => [],
+            ]
+        );
+    }
+
+    /**
+     * @covers Magento\Swatches\Block\Product\Renderer\Listing\Configurable::getSwatchAttributesData
+     */
+    public function testGetJsonSwatchConfigWithoutSwatches()
+    {
+        $this->prepareGetJsonSwatchConfig();
+        $this->configurable->setProduct($this->product);
+        $this->swatchHelper->expects($this->once())->method('getSwatchAttributesAsArray')
+            ->with($this->product)
+            ->willReturn([]);
+        $this->swatchHelper->expects($this->once())->method('getSwatchesByOptionsId')
+            ->willReturn([]);
+        $this->jsonEncoder->expects($this->once())->method('encode')->with([]);
+        $this->configurable->getJsonSwatchConfig();
+    }
+
+    /**
+     * @covers Magento\Swatches\Block\Product\Renderer\Listing\Configurable::getSwatchAttributesData
+     */
+    public function testGetJsonSwatchNotUsedInProductListing()
+    {
+        $this->prepareGetJsonSwatchConfig();
+        $this->configurable->setProduct($this->product);
+        $this->swatchHelper->expects($this->once())->method('getSwatchAttributesAsArray')
+            ->with($this->product)
+            ->willReturn([
+                1 => [
+                    'options' => [1 => 'testA', 3 => 'testB'],
+                    'use_product_image_for_swatch' => true,
+                    'used_in_product_listing' => false,
+                    'attribute_code' => 'code',
+                ],
+            ]);
+        $this->swatchHelper->expects($this->once())->method('getSwatchesByOptionsId')
+            ->willReturn([]);
+        $this->jsonEncoder->expects($this->once())->method('encode')->with([]);
+        $this->configurable->getJsonSwatchConfig();
+    }
+
+    /**
+     * @covers Magento\Swatches\Block\Product\Renderer\Listing\Configurable::getSwatchAttributesData
+     */
+    public function testGetJsonSwatchUsedInProductListing()
+    {
+        $products = [
+            1 => 'testA',
+            3 => 'testB'
+        ];
+        $expected =
+            [
+                'type' => null,
+                'value' => 'hello',
+                'label' => $products[3]
+            ];
+        $this->prepareGetJsonSwatchConfig();
+        $this->configurable->setProduct($this->product);
+        $this->swatchHelper->expects($this->once())->method('getSwatchAttributesAsArray')
+            ->with($this->product)
+            ->willReturn([
+                1 => [
+                    'options' => $products,
+                    'use_product_image_for_swatch' => true,
+                    'used_in_product_listing' => true,
+                    'attribute_code' => 'code',
+                ],
+            ]);
+        $this->swatchHelper->expects($this->once())->method('getSwatchesByOptionsId')
+            ->with([1, 3])
+            ->willReturn([
+                3 => ['type' => $expected['type'], 'value' => $expected['value']]
+            ]);
+        $this->jsonEncoder->expects($this->once())->method('encode');
+        $this->configurable->getJsonSwatchConfig();
+    }
+
+    private function prepareGetJsonSwatchConfig()
+    {
+        $product1 = $this->getMock('\Magento\Catalog\Model\Product', [], [], '', false);
+        $product1->expects($this->atLeastOnce())->method('isSaleable')->willReturn(true);
+        $product1->expects($this->any())->method('getData')->with('code')->willReturn(1);
+
+        $product2 = $this->getMock('\Magento\Catalog\Model\Product', [], [], '', false);
+        $product2->expects($this->atLeastOnce())->method('isSaleable')->willReturn(true);
+        $product2->expects($this->any())->method('getData')->with('code')->willReturn(3);
+
+        $simpleProducts = [$product1, $product2];
+        $configurableType = $this->getMock(
+            '\Magento\ConfigurableProduct\Model\Product\Type\Configurable',
+            [],
+            [],
+            '',
+            false
+        );
+        $configurableType->expects($this->atLeastOnce())->method('getUsedProducts')->with($this->product, null)
+            ->willReturn($simpleProducts);
+        $this->product->expects($this->any())->method('getTypeInstance')->willReturn($configurableType);
+
+        $productAttribute1 = $this->getMock('\Magento\Eav\Model\Entity\Attribute\AbstractAttribute', [], [], '', false);
+        $productAttribute1->expects($this->any())->method('getId')->willReturn(1);
+        $productAttribute1->expects($this->any())->method('getAttributeCode')->willReturn('code');
+
+        $attribute1 = $this->getMock(
+            '\Magento\ConfigurableProduct\Model\Product\Type\Configurable\Attribute',
+            ['getProductAttribute'],
+            [],
+            '',
+            false
+        );
+        $attribute1->expects($this->any())->method('getProductAttribute')->willReturn($productAttribute1);
+
+        $this->helper->expects($this->any())->method('getAllowAttributes')->with($this->product)
+            ->willReturn([$attribute1]);
+    }
+}
diff --git a/app/code/Magento/Swatches/view/frontend/web/js/SwatchRenderer.js b/app/code/Magento/Swatches/view/frontend/web/js/SwatchRenderer.js
index 22025f64994d33669c86583b4c89c83d444f35ec..8d1e7a2ecd97ccdd471ef37833887b9900cceabb 100644
--- a/app/code/Magento/Swatches/view/frontend/web/js/SwatchRenderer.js
+++ b/app/code/Magento/Swatches/view/frontend/web/js/SwatchRenderer.js
@@ -227,6 +227,7 @@ define([
                     'img': $main.find('.product-image-photo').attr('src')
                 }];
             }
+            this.productForm = this.element.parents(this.options.selectorProduct).find('form:first');
         },
 
         /**
@@ -260,6 +261,11 @@ define([
                         '<span class="' + classes.attributeSelectedOptionLabelClass + '"></span>';
                 }
 
+                if ($widget.productForm) {
+                    $widget.productForm.append(input);
+                    input = '';
+                }
+
                 // Create new control
                 container.append(
                     '<div class="' + classes.attributeClass + ' ' + item.code +
@@ -476,7 +482,10 @@ define([
 
             var $parent = $this.parents('.' + $widget.options.classes.attributeClass),
                 $label = $parent.find('.' + $widget.options.classes.attributeSelectedOptionLabelClass),
-                $input = $parent.find('.' + $widget.options.classes.attributeInput);
+                attributeId = $parent.attr('attribute-id'),
+                $input = $widget.productForm.find(
+                    '.' + $widget.options.classes.attributeInput + '[name="super_attribute[' + attributeId + ']"]'
+                );
 
             if ($this.hasClass('disabled')) {
                 return;
diff --git a/app/code/Magento/Widget/Model/Widget.php b/app/code/Magento/Widget/Model/Widget.php
index d89b77893eae9491a55b1454202f6a8cc59cc8b1..c25b76f8251e22708be579b71e5afde9b5f9f85f 100644
--- a/app/code/Magento/Widget/Model/Widget.php
+++ b/app/code/Magento/Widget/Model/Widget.php
@@ -50,6 +50,11 @@ class Widget
      */
     protected $conditionsHelper;
 
+    /**
+     * @var \Magento\Framework\Math\Random
+     */
+    private $mathRandom;
+
     /**
      * @param \Magento\Framework\Escaper $escaper
      * @param \Magento\Widget\Model\Config\Data $dataStorage
@@ -74,6 +79,20 @@ class Widget
         $this->conditionsHelper = $conditionsHelper;
     }
 
+    /**
+     * @return \Magento\Framework\Math\Random
+     *
+     * @deprecated
+     */
+    private function getMathRandom()
+    {
+        if ($this->mathRandom === null) {
+            $this->mathRandom = \Magento\Framework\App\ObjectManager::getInstance()
+                ->get('\Magento\Framework\Math\Random');
+        }
+        return $this->mathRandom;
+    }
+
     /**
      * Return widget config based on its class type
      *
@@ -289,7 +308,7 @@ class Widget
             } elseif (trim($value) == '') {
                 $widget = $this->getConfigAsObject($type);
                 $parameters = $widget->getParameters();
-                if (isset($parameters[$name]) && is_object($parameters[$name])) {
+                if (is_object($parameters[$name])) {
                     $value = $parameters[$name]->getValue();
                 }
             }
@@ -297,6 +316,15 @@ class Widget
                 $directive .= sprintf(' %s="%s"', $name, $value);
             }
         }
+
+        if ((bool)$params['show_pager']) {
+            $directive .= sprintf(
+                ' %s="%s"',
+                'page_var_name',
+                'p' . $this->getMathRandom()->getRandomString(5, \Magento\Framework\Math\Random::CHARS_LOWERS)
+            );
+        }
+
         $directive .= '}}';
 
         if ($asIs) {
diff --git a/app/code/Magento/Widget/Model/Widget/Instance.php b/app/code/Magento/Widget/Model/Widget/Instance.php
index 7bccc0812d1e7ff79a573e31600a46fd9a269430..f6142f3d7e0ca5cb58d3bbcc00031d720dd3f6d1 100644
--- a/app/code/Magento/Widget/Model/Widget/Instance.php
+++ b/app/code/Magento/Widget/Model/Widget/Instance.php
@@ -241,8 +241,16 @@ class Instance extends \Magento\Framework\Model\AbstractModel
         if (is_array($this->getData('store_ids'))) {
             $this->setData('store_ids', implode(',', $this->getData('store_ids')));
         }
-        if (is_array($this->getData('widget_parameters'))) {
-            $this->setData('widget_parameters', serialize($this->getData('widget_parameters')));
+
+        $parameters = $this->getData('widget_parameters');
+        if (is_array($parameters)) {
+            if ((bool)$parameters['show_pager'] && !array_key_exists('page_var_name', $parameters)) {
+                $parameters['page_var_name'] = 'p' . $this->mathRandom->getRandomString(
+                    5,
+                    \Magento\Framework\Math\Random::CHARS_LOWERS
+                );
+            }
+            $this->setData('widget_parameters', serialize($parameters));
         }
         $this->setData('page_groups', $tmpPageGroups);
         $this->setData('page_group_ids', $pageGroupIds);
diff --git a/app/code/Magento/Widget/Test/Unit/Model/WidgetTest.php b/app/code/Magento/Widget/Test/Unit/Model/WidgetTest.php
index 7ccea8d305c6fff851b9f05e21d4d9c2be97252b..16bcb5453247232b86db8166ac9159700393d480 100644
--- a/app/code/Magento/Widget/Test/Unit/Model/WidgetTest.php
+++ b/app/code/Magento/Widget/Test/Unit/Model/WidgetTest.php
@@ -17,15 +17,24 @@ class WidgetTest extends \PHPUnit_Framework_TestCase
      */
     protected $widget;
 
+    /**
+     * @var \Magento\Widget\Helper\Conditions
+     */
+    private $conditionsHelper;
+
     public function setUp()
     {
         $this->dataStorageMock = $this->getMockBuilder('Magento\Widget\Model\Config\Data')
             ->disableOriginalConstructor()
             ->getMock();
+        $this->conditionsHelper = $this->getMockBuilder('\Magento\Widget\Helper\Conditions')
+            ->setMethods(['encode'])
+            ->disableOriginalConstructor()
+            ->getMock();
         $objectManagerHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
         $this->widget = $objectManagerHelper->getObject(
             'Magento\Widget\Model\Widget',
-            ['dataStorage' => $this->dataStorageMock]
+            ['dataStorage' => $this->dataStorageMock, 'conditionsHelper' => $this->conditionsHelper]
         );
     }
 
@@ -119,4 +128,37 @@ class WidgetTest extends \PHPUnit_Framework_TestCase
         $this->assertInstanceOf('Magento\Framework\DataObject', $resultObject);
         $this->assertSame([], $resultObject->getData());
     }
+
+    public function testGetWidgetDeclaration()
+    {
+        $mathRandomMock = $this->getMock('\Magento\Framework\Math\Random', ['getRandomString'], [], '', false);
+        $mathRandomMock->expects($this->any())->method('getRandomString')->willReturn('asdf');
+        $reflection = new \ReflectionClass(get_class($this->widget));
+        $reflectionProperty = $reflection->getProperty('mathRandom');
+        $reflectionProperty->setAccessible(true);
+        $reflectionProperty->setValue($this->widget, $mathRandomMock);
+
+        $conditions = [
+            [
+                'type' => 'Magento\CatalogWidget\Model\Rule\Condition\Combine',
+                'aggregator' => 'all',
+                'value' => '1',
+                'new_child' => ''
+            ]
+        ];
+        $params = [
+            'title' => 'my widget',
+            'show_pager' => '1',
+            'products_per_page' => '5',
+            'products_count' => '10',
+            'template' => 'product/widget/content/grid.phtml',
+            'conditions' => $conditions
+        ];
+        $this->conditionsHelper->expects($this->once())->method('encode')->with($conditions)
+            ->willReturn('encoded-conditions-string');
+        $result = $this->widget->getWidgetDeclaration('Magento\CatalogWidget\Block\Product\ProductsList', $params);
+        $this->assertContains('{{widget type="Magento\CatalogWidget\Block\Product\ProductsList"', $result);
+        $this->assertContains('conditions_encoded="encoded-conditions-string"', $result);
+        $this->assertContains('page_var_name="pasdf"}}', $result);
+    }
 }
diff --git a/app/code/Magento/Wishlist/Block/AddToWishlist.php b/app/code/Magento/Wishlist/Block/AddToWishlist.php
new file mode 100644
index 0000000000000000000000000000000000000000..db6dae099fe21e1dccf97887e7c4dc00ce280177
--- /dev/null
+++ b/app/code/Magento/Wishlist/Block/AddToWishlist.php
@@ -0,0 +1,78 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\Wishlist\Block;
+
+/**
+ * Wishlist js plugin initialization block
+ */
+class AddToWishlist extends \Magento\Framework\View\Element\Template
+{
+    /**
+     * Product types
+     *
+     * @var array|null
+     */
+    private $productTypes;
+
+    /**
+     * @param \Magento\Framework\View\Element\Template\Context $context
+     * @param array $data
+     */
+    public function __construct(
+        \Magento\Framework\View\Element\Template\Context $context,
+        array $data = []
+    ) {
+        parent::__construct(
+            $context,
+            $data
+        );
+    }
+
+    /**
+     * Returns wishlist widget options
+     *
+     * @return array
+     */
+    public function getWishlistOptions()
+    {
+        return ['productType' => $this->getProductTypes()];
+    }
+
+    /**
+     * Returns an array of product types
+     *
+     * @return array|null
+     * @throws \Magento\Framework\Exception\LocalizedException
+     */
+    private function getProductTypes()
+    {
+        if ($this->productTypes === null) {
+            $this->productTypes = [];
+            $block = $this->getLayout()->getBlock('category.products.list');
+            if ($block) {
+                $productCollection = $block->getLoadedProductCollection();
+                /** @var $product \Magento\Catalog\Model\Product */
+                foreach ($productCollection as $product) {
+                    $productTypes[] = $product->getTypeId();
+                }
+                $this->productTypes = array_unique($productTypes);
+            }
+        }
+        return $this->productTypes;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function _toHtml()
+    {
+        if (!$this->getProductTypes()) {
+            return '';
+        }
+        return parent::_toHtml();
+    }
+}
diff --git a/app/code/Magento/Wishlist/view/frontend/layout/catalog_category_view.xml b/app/code/Magento/Wishlist/view/frontend/layout/catalog_category_view.xml
index 907e418b935d5d5ae5ba9b3e9a6b69b403f02410..3cdce06ba62b3c89970b36303dcccbbb33e6ea08 100644
--- a/app/code/Magento/Wishlist/view/frontend/layout/catalog_category_view.xml
+++ b/app/code/Magento/Wishlist/view/frontend/layout/catalog_category_view.xml
@@ -15,6 +15,9 @@
                     </argument>
                 </arguments>
             </block>
+            <referenceContainer name="category.product.list.additional">
+                <block class="Magento\Wishlist\Block\AddToWishlist" template="Magento_Wishlist::addto.phtml" />
+            </referenceContainer>
         </referenceContainer>
     </body>
 </page>
diff --git a/app/code/Magento/Wishlist/view/frontend/templates/addto.phtml b/app/code/Magento/Wishlist/view/frontend/templates/addto.phtml
new file mode 100644
index 0000000000000000000000000000000000000000..98c7055cb63af1da1d48652772e3fa907ebb061d
--- /dev/null
+++ b/app/code/Magento/Wishlist/view/frontend/templates/addto.phtml
@@ -0,0 +1,15 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+?>
+<script type="text/x-magento-init">
+    {
+        "body": {
+            "addToWishlist": <?php /* @noEscape */ echo $this->helper('Magento\Framework\Json\Helper\Data')
+                ->jsonEncode($block->getWishlistOptions());
+            ?>
+        }
+    }
+</script>
diff --git a/app/code/Magento/Wishlist/view/frontend/web/js/add-to-wishlist.js b/app/code/Magento/Wishlist/view/frontend/web/js/add-to-wishlist.js
index 79ff842fdd91557c34f979e590aa87e152f28d53..15390d6f8e1c9ecd9a97811cd66e6c9d8549ba1b 100644
--- a/app/code/Magento/Wishlist/view/frontend/web/js/add-to-wishlist.js
+++ b/app/code/Magento/Wishlist/view/frontend/web/js/add-to-wishlist.js
@@ -22,13 +22,25 @@ define([
             this._bind();
         },
         _bind: function() {
-            var changeCustomOption = 'change ' + this.options.customOptionsInfo,
-                changeQty = 'change ' + this.options.qtyInfo,
-                changeProductInfo = 'change ' + this.options[this.options.productType + 'Info'],
+            var options = this.options,
+                changeCustomOption = 'change ' + options.customOptionsInfo,
+                changeQty = 'change ' + options.qtyInfo,
                 events = {};
+
+            if ('productType' in options) {
+                if (typeof options.productType === 'string') {
+                    options.productType = [options.productType];
+                }
+            } else {
+                options.productType = [];
+            }
+
             events[changeCustomOption] = '_updateWishlistData';
-            events[changeProductInfo] = '_updateWishlistData';
             events[changeQty] = '_updateWishlistData';
+            options.productType.forEach(function (type) {
+                events['change ' + options[type + 'Info']] = '_updateWishlistData';
+            });
+
             this._on(events);
         },
         _updateWishlistData: function(event) {
@@ -90,10 +102,11 @@ define([
             return result;
         },
         _getElementData: function(element) {
+            element = $(element);
             var data = {},
-                elementName = $(element).data('selector'),
-                elementValue = $(element).val();
-            if ($(element).is('select[multiple]') && elementValue !== null) {
+                elementName = element.data('selector') ? element.data('selector') : element.attr('name'),
+                elementValue = element.val();
+            if (element.is('select[multiple]') && elementValue !== null) {
                 if (elementName.substr(elementName.length - 2) == '[]') {
                     elementName = elementName.substring(0, elementName.length - 2);
                 }
diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Handler/CatalogProductSimple/Webapi.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Handler/CatalogProductSimple/Webapi.php
index 853d6de34a482a951f2e18e76ec3c517e62ee016..7ea6af446dc2d7f98941b481aeccd16112c74362 100644
--- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Handler/CatalogProductSimple/Webapi.php
+++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Handler/CatalogProductSimple/Webapi.php
@@ -60,7 +60,8 @@ class Webapi extends AbstractWebApi implements CatalogProductSimpleInterface
         'options',
         'media_gallery_entries',
         'tier_prices',
-        'extension_attributes'
+        'extension_attributes',
+        'custom_attributes'
     ];
 
     /**
diff --git a/dev/tests/functional/tests/app/Magento/Weee/Test/Block/Product/Price.php b/dev/tests/functional/tests/app/Magento/Weee/Test/Block/Product/Price.php
index 19b7a0cb01f43c56a6c9586d9c7a3642c093ef74..03636a56104aade2474f5f60d14c99674b28e8d2 100644
--- a/dev/tests/functional/tests/app/Magento/Weee/Test/Block/Product/Price.php
+++ b/dev/tests/functional/tests/app/Magento/Weee/Test/Block/Product/Price.php
@@ -17,6 +17,9 @@ class Price extends \Magento\Catalog\Test\Block\AbstractPriceBlock
      * @var array
      */
     protected $mapTypePrices = [
+        'regular_price' => [
+            'selector' => '[data-price-type="finalPrice"] .price',
+        ],
         'fpt_price' => [
             'selector' => '[data-price-type="weee"] .price',
         ],
diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_url_key.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_url_key.php
new file mode 100644
index 0000000000000000000000000000000000000000..5ca9fa01554aefb9c495b91bc60def699ccf72c6
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_url_key.php
@@ -0,0 +1,37 @@
+<?php
+/**
+ * Copyright © 2015 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+/** @var $product \Magento\Catalog\Model\Product */
+$product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create('Magento\Catalog\Model\Product');
+$product->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE)
+    ->setAttributeSetId(4)
+    ->setWebsiteIds([1])
+    ->setName('Simple Product')
+    ->setSku('simple1')
+    ->setPrice(10)
+    ->setDescription('Description with <b>html tag</b>')
+    ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH)
+    ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED)
+    ->setCategoryIds([2])
+    ->setStockData(['use_config_manage_stock' => 1, 'qty' => 100, 'is_qty_decimal' => 0, 'is_in_stock' => 1])
+    ->setUrlKey('url-key')
+    ->save();
+
+/** @var $product \Magento\Catalog\Model\Product */
+$product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create('Magento\Catalog\Model\Product');
+$product->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE)
+    ->setAttributeSetId(4)
+    ->setWebsiteIds([1])
+    ->setName('Simple Product 2')
+    ->setSku('simple2')
+    ->setPrice(10)
+    ->setDescription('Description with <b>html tag</b>')
+    ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH)
+    ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED)
+    ->setCategoryIds([2])
+    ->setStockData(['use_config_manage_stock' => 1, 'qty' => 100, 'is_qty_decimal' => 0, 'is_in_stock' => 1])
+    ->setUrlKey('url-key2')
+    ->save();
diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Export/ProductTest.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Export/ProductTest.php
index 68f9b09a1afb1965d96a11d312e17b61c98e1e5c..2ed3c31c7dba3f831a3cd89bf49c07ced80a4593 100644
--- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Export/ProductTest.php
+++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Export/ProductTest.php
@@ -82,6 +82,19 @@ class ProductTest extends \PHPUnit_Framework_TestCase
         $this->assertContains('max_characters=10', $exportData);
     }
 
+    /**
+     * @magentoDataFixture Magento/CatalogImportExport/_files/product_export_with_product_links_data.php
+     */
+    public function testExportWithProductLinks()
+    {
+        $this->model->setWriter(
+            \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(
+                'Magento\ImportExport\Model\Export\Adapter\Csv'
+            )
+        );
+        $this->assertNotEmpty($this->model->export());
+    }
+
     /**
      * Verify that all stock item attribute values are exported (aren't equal to empty string)
      *
diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php
index bbc844340bc3da5e5a2c963d806999a98ba6fa88..1613a1c28c50d2f6aa7f10fd0de9935781728ecd 100644
--- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php
+++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php
@@ -1052,4 +1052,113 @@ class ProductTest extends \Magento\TestFramework\Indexer\TestCase
 
         $this->assertTrue($errors->getErrorsCount() == 0);
     }
+
+    /**
+     * @magentoAppArea adminhtml
+     * @magentoDbIsolation enabled
+     * @magentoAppIsolation enabled
+     */
+    public function testProductWithLinks()
+    {
+        $linksData = [
+            'upsell' => [
+                'simple1' => '3',
+                'simple3' => '1'
+            ],
+            'crosssell' => [
+                'simple2' => '1',
+                'simple3' => '2'
+            ],
+            'related' => [
+                'simple1' => '2',
+                'simple2' => '1'
+            ]
+        ];
+        // import data from CSV file
+        $pathToFile = __DIR__ . '/_files/products_to_import_with_product_links.csv';
+        $filesystem = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(
+            'Magento\Framework\Filesystem'
+        );
+
+        $directory = $filesystem->getDirectoryWrite(DirectoryList::ROOT);
+        $source = $this->objectManager->create(
+            '\Magento\ImportExport\Model\Import\Source\Csv',
+            [
+                'file' => $pathToFile,
+                'directory' => $directory
+            ]
+        );
+        $errors = $this->_model->setSource(
+            $source
+        )->setParameters(
+            [
+                'behavior' => \Magento\ImportExport\Model\Import::BEHAVIOR_APPEND,
+                'entity' => 'catalog_product'
+            ]
+        )->validateData();
+
+        $this->assertTrue($errors->getErrorsCount() == 0);
+        $this->_model->importData();
+
+        $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+        $resource = $objectManager->get('Magento\Catalog\Model\ResourceModel\Product');
+        $productId = $resource->getIdBySku('simple4');
+        /** @var \Magento\Catalog\Model\Product $product */
+        $product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(
+            'Magento\Catalog\Model\Product'
+        );
+        $product->load($productId);
+        $productLinks = [
+            'upsell' => $product->getUpSellProducts(),
+            'crosssell' => $product->getCrossSellProducts(),
+            'related' => $product->getRelatedProducts()
+        ];
+        $importedProductLinks = [];
+        foreach ($productLinks as $linkType => $linkedProducts) {
+            foreach ($linkedProducts as $linkedProductData) {
+                $importedProductLinks[$linkType][$linkedProductData->getSku()] = $linkedProductData->getPosition();
+            }
+        }
+        $this->assertEquals($linksData, $importedProductLinks);
+    }
+
+    /**
+     * @magentoDataFixture Magento/Catalog/_files/product_simple_with_url_key.php
+     * @magentoAppIsolation enabled
+     */
+    public function testExistingProductWithUrlKeys()
+    {
+        $this->markTestSkipped('Test must be unskiped after implementation MAGETWO-48871');
+        $products = [
+            'simple1' => 'url-key1',
+            'simple2' => 'url-key2',
+            'simple3' => 'url-key3'
+        ];
+        $filesystem = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()
+            ->create('Magento\Framework\Filesystem');
+        $directory = $filesystem->getDirectoryWrite(DirectoryList::ROOT);
+        $source = $this->objectManager->create(
+            '\Magento\ImportExport\Model\Import\Source\Csv',
+            [
+                'file' => __DIR__ . '/_files/products_to_import_with_valid_url_keys.csv',
+                'directory' => $directory
+            ]
+        );
+
+        $errors = $this->_model->setParameters(
+            ['behavior' => \Magento\ImportExport\Model\Import::BEHAVIOR_APPEND, 'entity' => 'catalog_product']
+        )->setSource(
+            $source
+        )->validateData();
+
+        $this->assertTrue($errors->getErrorsCount() == 0);
+        $this->_model->importData();
+
+        $productRepository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(
+            'Magento\Catalog\Api\ProductRepositoryInterface'
+        );
+        foreach ($products as $productSku => $productUrlKey) {
+            $this->assertEquals($productUrlKey, $productRepository->get($productSku)->getUrlKey());
+        }
+    }
 }
diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import_with_product_links.csv b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import_with_product_links.csv
new file mode 100644
index 0000000000000000000000000000000000000000..8e5733ebe23cca2c11186a1da990b533522d5b2e
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import_with_product_links.csv
@@ -0,0 +1,5 @@
+sku,product_type,store_view_code,name,price,qty,attribute_set_code,related_skus,related_position,crosssell_skus,crosssell_position,upsell_skus,upsell_position
+simple1,simple,,simple 1,25,10,Default,,,,,,
+simple2,simple,,simple 2,34,10,Default,,,,,,
+simple3,simple,,simple 3,58,10,Default,,,,,,
+simple4,simple,,simple 4,67,10,Default,"simple1,simple2","2,1","simple2,simple3","1,2","simple1,simple3","3,1"
\ No newline at end of file
diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import_with_valid_url_keys.csv b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import_with_valid_url_keys.csv
new file mode 100644
index 0000000000000000000000000000000000000000..06e66d945b7fc33324ad0b290838d49022cf3589
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import_with_valid_url_keys.csv
@@ -0,0 +1,4 @@
+sku,product_type,store_view_code,name,price,attribute_set_code,url_key
+simple1,simple,,"simple 1",25,Default,url-key1
+simple2,simple,,"simple 2",34,Default,url-key2
+simple3,simple,,"simple 3",58,Default,url-key3
diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/product_export_with_product_links_data.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/product_export_with_product_links_data.php
new file mode 100644
index 0000000000000000000000000000000000000000..e7503b8a3431e16af487f73f1ca5ee08634eca5e
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/product_export_with_product_links_data.php
@@ -0,0 +1,47 @@
+<?php
+/**
+ * Copyright © 2015 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+require dirname(dirname(__DIR__)) . '/Catalog/_files/category.php';
+require dirname(dirname(__DIR__)) . '/Store/_files/second_store.php';
+require dirname(dirname(__DIR__)) . '/Catalog/_files/products_with_multiselect_attribute.php';
+
+$productModel = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create('Magento\Catalog\Model\Product');
+
+$productModel->setTypeId(
+    \Magento\Catalog\Model\Product\Type::TYPE_SIMPLE
+)->setId(
+    1
+)->setAttributeSetId(
+    4
+)->setName(
+    'New Product'
+)->setSku(
+    'simple'
+)->setPrice(
+    10
+)->setTierPrice(
+    [0 => ['website_id' => 0, 'cust_group' => 0, 'price_qty' => 3, 'price' => 8]]
+)->setVisibility(
+    \Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH
+)->setStatus(
+    \Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED
+)->setWebsiteIds(
+    [1]
+)->setCateroryIds(
+    []
+)->setStockData(
+    ['qty' => 100, 'is_in_stock' => 1]
+)->setCanSaveCustomOptions(
+    true
+)->setCategoryIds(
+    [333]
+)->setUpSellLinkData(
+    [$product->getId() => ['position' => 1]]
+)->setCrossSellLinkData(
+    [$product->getId() => ['position' => 2]]
+)->setRelatedLinkData(
+    [$product->getId() => ['position' => 3]]
+)->save();